import os
import sys
import trinity
import blue
import bluepy
import uthread
import log
import yaml
import copy
import types
import TextureCompositor.TextureCompositor as tc
import RenderTargetManager.RenderTargetManager as rtm
import paperDoll as PD
TC_MARKERS_ENABLED = False

def AddMarker(m):
    if TC_MARKERS_ENABLED:
        trinity.graphs.AddMarker('frameTime', m)


RTM = rtm.RenderTargetManager()
COMPRESS_DXT1 = trinity.TR2DXT_COMPRESS_SQUISH_DXT1
COMPRESS_DXT5 = trinity.TR2DXT_COMPRESS_SQUISH_DXT5
COMPRESS_DXT5n = trinity.TR2DXT_COMPRESS_RT_DXT5N
COMPRESS_YCoCgDXT5 = trinity.TR2DXT_COMPRESS_RT_YCOCGDXT5
COMPRESS_3Dc = trinity.TR2DXT_COMPRESS_RT_3DC

class UpdateRuleBundle(object):
    __guid__ = 'paperDoll.UpdateRuleBundle'
    blendShapesOnly = property(fget=lambda self: self._UpdateRuleBundle__blendShapesOnly)
    decalsOnly = property(fget=lambda self: self._UpdateRuleBundle__decalsOnly)

    def setdoDecals(self, value):
        self._UpdateRuleBundle__doDecals = value


    doDecals = property(fget=lambda self: (self._UpdateRuleBundle__doDecals or self._UpdateRuleBundle__decalsOnly), fset=setdoDecals)

    def __init__(self):
        self.forcesLooseTop = False
        self.hidesBootShin = False
        self.undoMaskedShaders = False
        self.rebuildHair = False
        self.mapsToComposite = list(PD.MAPS)
        self._UpdateRuleBundle__blendShapesOnly = False
        self._UpdateRuleBundle__decalsOnly = False
        self.meshesAddedOrRemoved = False
        self.partsToComposite = None
        self._UpdateRuleBundle__doDecals = False



    def __str__(self):
        s = '{0}\n'.format(id(self))
        for (key, value,) in self.__dict__.iteritems():
            s += '\t{0} := {1}\n'.format(key, value)

        return s



    def SetBlendShapesOnly(self, value):
        self._UpdateRuleBundle__blendShapesOnly = value



    def SetDecalsOnly(self, value):
        self._UpdateRuleBundle__decalsOnly = value




class MapBundle(object):
    __guid__ = 'paperDoll.MapBundle'

    def __init__(self):
        self.diffuseMap = None
        self.specularMap = None
        self.normalMap = None
        self.maskMap = None
        self.hashKeys = {}



    def __getitem__(self, idx):
        if (idx == PD.DIFFUSE_MAP):
            return self.diffuseMap
        if (idx == PD.SPECULAR_MAP):
            return self.specularMap
        if (idx == PD.NORMAL_MAP):
            return self.normalMap
        if (idx == PD.MASK_MAP):
            return self.maskMap
        raise AttributeError()



    def SetMapByTypeIndex(self, idx, map, hashKey):
        if (idx == PD.DIFFUSE_MAP):
            self.diffuseMap = map
        elif (idx == PD.SPECULAR_MAP):
            self.specularMap = map
        elif (idx == PD.NORMAL_MAP):
            self.normalMap = map
        elif (idx == PD.MASK_MAP):
            self.maskMap = map
        else:
            raise ValueError('paperDoll::MapBundle::SetMapByTypeIndex - Index provided is not defined or supported. It must be defined in paperDoll.MAPS')
        self.hashKeys[idx] = hashKey



    def AllMapsGood(self):
        for each in iter(self):
            if ((not each) or (not each.isGood)):
                return False

        return True



    def __iter__(self):
        yield self.diffuseMap
        yield self.specularMap
        yield self.normalMap
        yield self.maskMap



    def ReCreate(self):
        del self.diffuseMap
        del self.specularMap
        del self.normalMap
        del self.maskMap
        self.diffuseMap = None
        self.specularMap = None
        self.normalMap = None
        self.maskMap = None
        self.hashKeys = {}



    def __del__(self):
        del self.diffuseMap
        del self.specularMap
        del self.normalMap
        del self.maskMap




class PerformanceOptions:
    __guid__ = 'paperDoll.PerformanceOptions'
    useLodForRedfiles = False
    if hasattr(const, 'PAPERDOLL_LOD_RED_FILES'):
        useLodForRedfiles = const.PAPERDOLL_LOD_RED_FILES
    collapseShadowMesh = False
    if hasattr(const, 'PAPERDOLL_COLLAPSE_SHADOWMESH'):
        collapseShadowMesh = const.PAPERDOLL_COLLAPSE_SHADOWMESH
    collapseMainMesh = False
    if hasattr(const, 'PAPERDOLL_COLLAPSE_MAINMESH'):
        collapseMainMesh = const.PAPERDOLL_COLLAPSE_MAINMESH
    collapsePLPMesh = False
    if hasattr(const, 'PAPERDOLL_COLLAPSE_PLPMESH'):
        collapsePLPMesh = const.PAPERDOLL_COLLAPSE_PLPMESH
    shadowLod = 2
    collapseVerbose = False
    updateFreq = {}

    @staticmethod
    def EnableOptimizations():
        PerformanceOptions.useLodForRedfiles = True
        PerformanceOptions.collapseShadowMesh = True
        PerformanceOptions.collapseMainMesh = True
        PerformanceOptions.collapsePLPMesh = True
        PerformanceOptions.shadowLod = 2
        PerformanceOptions.updateFreq = {0: 0,
         1: 20,
         2: 8}
        trinity.settings.SetValue('skinnedLowDetailThreshold', 250)
        trinity.settings.SetValue('skinnedMediumDetailThreshold', 650)




class CompressionSettings:
    __guid__ = 'paperDoll.CompressionSettings'

    @bluepy.CCP_STATS_ZONE_METHOD
    def __init__(self, compressTextures = True, generateMipmap = False):
        self.compressTextures = compressTextures
        self.generateMipmap = generateMipmap
        self.compressNormalMap = True
        self.compressSpecularMap = True
        self.compressDiffuseMap = True
        self.compressMaskMap = True



    @bluepy.CCP_STATS_ZONE_METHOD
    def __repr__(self):
        return ('%i%i%i%i%i%i' % (self.compressTextures,
         self.compressNormalMap,
         self.compressSpecularMap,
         self.compressDiffuseMap,
         self.compressMaskMap,
         self.generateMipmap))



    @bluepy.CCP_STATS_ZONE_METHOD
    def SetMapCompression(self, compressNormalMap = True, compressSpecularMap = True, compressDiffuseMap = True, compressMaskMap = True):
        self.compressNormalMap = compressNormalMap
        self.compressSpecularMap = compressSpecularMap
        self.compressDiffuseMap = compressDiffuseMap
        self.compressMaskMap = compressMaskMap



    @bluepy.CCP_STATS_ZONE_METHOD
    def AllowCompress(self, textureType):
        if (textureType == PD.DIFFUSE_MAP):
            return self.compressDiffuseMap
        if (textureType == PD.SPECULAR_MAP):
            return self.compressSpecularMap
        if (textureType == PD.NORMAL_MAP):
            return self.compressNormalMap
        if (textureType == PD.MASK_MAP):
            return self.compressMaskMap




class Factory(object,
 CompressionSettings,
 PD.ModifierLoader):
    __guid__ = 'paperDoll.Factory'
    shaderPreload = None
    texturePreload = None

    @bluepy.CCP_STATS_ZONE_METHOD
    def __init__(self, gender = None):
        object.__init__(self)
        CompressionSettings.__init__(self, compressTextures=False)
        PD.ModifierLoader.__init__(self)
        if gender:
            log.LogWarn('PaperDoll: Deprication warning, Gender is no longer a property of the Factory.')
        self.allowTextureCache = False
        self.saveTexturesToDisk = False
        self.verbose = False
        self.skipCompositing = False
        self.PreloadedGenericHeadModifiers = {PD.GENDER.MALE: None,
         PD.GENDER.FEMALE: None}

        def PreLoadGenericHeadsOnceLoaded_t():
            while not self.IsLoaded:
                blue.synchro.Yield()

            self.PreloadedGenericHeadModifiers[PD.GENDER.MALE] = self.CollectBuildData('head/head_generic', self.GetOptionsByGender(PD.GENDER.MALE))
            self.PreloadedGenericHeadModifiers[PD.GENDER.FEMALE] = self.CollectBuildData('head/head_generic', self.GetOptionsByGender(PD.GENDER.FEMALE))


        uthread.new(PreLoadGenericHeadsOnceLoaded_t)
        uthread.new(Factory.PreloadShaders)



    @bluepy.CCP_STATS_ZONE_METHOD
    @staticmethod
    def PreloadShaders():
        if (Factory.shaderPreload is not None):
            return 
        resFile = blue.ResFile()
        Factory.shaderPreload = []
        for path in PD.EFFECT_PRELOAD_PATHS:
            fullPath = ('res:/graphics/effect/managed/interior/avatar/' + path)
            if resFile.FileExists(fullPath):
                effect = trinity.Tr2Effect()
                effect.effectFilePath = fullPath
                Factory.shaderPreload.append(effect)

        for path in PD.COMPOSITE_PRELOAD_PATHS:
            if resFile.FileExists(fullPath):
                fullPath = ('res:/graphics/effect/utility/compositing/' + path)
                effect = trinity.Tr2Effect()
                effect.effectFilePath = fullPath
                Factory.shaderPreload.append(effect)

        Factory.texturePreload = []
        for path in PD.TEXTURE_PRELOAD_PATHS:
            if resFile.FileExists(path):
                texture = trinity.TriTexture2DParameter()
                texture.resourcePath = path
                Factory.texturePreload.append(texture)




    @staticmethod
    @bluepy.CCP_STATS_ZONE_FUNCTION
    def ApplyMorphTargetsToMeshes(meshes, morphTargets, blendShapeMeshCache = None, meshGeometryResPaths = None, doYield = True):
        if (blendShapeMeshCache is None):
            blendShapeMeshCache = {}
        loadingGrannyResources = {}
        meshGenerator = (mesh for mesh in iter(meshes) if not blendShapeMeshCache.get(mesh.name))
        for mesh in meshGenerator:
            index = mesh.meshIndex
            path = mesh.geometryResPath
            if ((not path) and (meshGeometryResPaths is not None)):
                path = meshGeometryResPaths.get(mesh.name, None)
                if ((not path) and mesh.name[-1].isdigit()):
                    for meshName in meshGeometryResPaths:
                        if meshName.startswith(mesh.name[:-1]):
                            path = meshGeometryResPaths.get(meshName, None)
                            break

                    if ((not path) and mesh.name[-2].isdigit):
                        for meshName in meshGeometryResPaths:
                            if meshName.startswith(mesh.name[:-2]):
                                path = meshGeometryResPaths.get(meshName, None)
                                break

            if not path:
                continue
            gr2 = path
            grannyResource = blue.resMan.GetResource(gr2, 'raw')
            loadingGrannyResources[mesh.name] = grannyResource

        while doYield:
            doYield = False
            for lgr in loadingGrannyResources.itervalues():
                if lgr.isLoading:
                    doYield = True
                    break

            blue.pyos.BeNice()

        if loadingGrannyResources:
            meshCount = len(meshes)
            for mIdx in xrange(meshCount):
                mesh = meshes[mIdx]
                grannyResource = loadingGrannyResources.get(mesh.name)
                index = mesh.meshIndex
                if (grannyResource and (grannyResource.isGood and (grannyResource.meshCount > index))):
                    geometryRes = grannyResource.CreateGeometryRes()
                    count = grannyResource.GetMeshMorphCount(index)
                    morphNames = []
                    for i in xrange(count):
                        name = grannyResource.GetMeshMorphName(index, i)
                        morphNames.append(''.join((letter.lower() for letter in name if not letter.isdigit())))

                    blendShapeMeshCache[mesh.name] = (grannyResource,
                     geometryRes,
                     morphNames)
                else:
                    if loadingGrannyResources.get(mesh.name):
                        del loadingGrannyResources[mesh.name]

        meshGenerator = (mesh for mesh in iter(meshes) if blendShapeMeshCache.get(mesh.name))
        for mesh in meshGenerator:
            (grannyResource, geometryRes, morphNames,) = blendShapeMeshCache[mesh.name]
            weights = []
            for name in morphNames:
                weight = morphTargets.get(name, 0.0)
                weights.append(weight)

            if ((len(weights) > 0) and grannyResource.BakeBlendshape(mesh.meshIndex, weights, geometryRes)):
                if (mesh.geometry != geometryRes):
                    mesh.SetGeometryRes(geometryRes)
            else:
                del blendShapeMeshCache[mesh.name]




    @bluepy.CCP_STATS_ZONE_METHOD
    def BindGlobalMaps(self, meshes):
        effects = []
        for mesh in iter(meshes):
            effects += PD.GetEffectsFromMesh(mesh)

        for each in iter(effects):
            if (each is None):
                continue
            NdotLres = None
            FresnelLookupMapRes = None
            if (type(each) != trinity.Tr2ShaderMaterial):
                for r in iter(each.resources):
                    if (r.name == 'ColorNdotLLookupMap'):
                        NdotLres = r
                    else:
                        if (r.name == 'FresnelLookupMap'):
                            FresnelLookupMapRes = r

            if (NdotLres is None):
                res = trinity.TriTexture2DParameter()
                res.name = 'ColorNdotLLookupMap'
                res.resourcePath = 'res:/Texture/Global/NdotLLibrary.png'
                if (type(each) == trinity.Tr2ShaderMaterial):
                    each.parameters[res.name] = res
                else:
                    each.resources.append(res)
            else:
                NdotLres.resourcePath = 'res:/Texture/Global/NdotLLibrary.png'
            if (FresnelLookupMapRes is None):
                res = trinity.TriTexture2DParameter()
                res.name = 'FresnelLookupMap'
                res.resourcePath = PD.FRESNEL_LOOKUP_MAP
                if (type(each) == trinity.Tr2ShaderMaterial):
                    each.parameters[res.name] = res
                else:
                    each.resources.append(res)
            else:
                FresnelLookupMapRes.resourcePath = PD.FRESNEL_LOOKUP_MAP




    @bluepy.CCP_STATS_ZONE_METHOD
    def ClearAllCachedMaps(self):
        cachePath = self.GetMapCachePath()
        fileSystemPath = blue.rot.PathToFilename(cachePath)
        for (root, dirs, files,) in os.walk(fileSystemPath):
            for f in iter(files):
                os.remove(os.path.join(root, f))





    def GetMapCachePath(self):
        return blue.rot.PathToFilename('cache:/Avatars/cachedMaps')



    def GetCacheFilePath(self, cachePath, hashKey, lodLevel, mapType):
        hashKey = str(hashKey).replace('-', '_')
        mapType = str(mapType)
        return os.path.join(cachePath, str(lodLevel), ((mapType + hashKey) + '.dds'))



    @bluepy.CCP_STATS_ZONE_METHOD
    def FindCachedTexture(self, hashKey, lodLevel, mapType):
        cachePath = self.GetMapCachePath()
        filePath = self.GetCacheFilePath(cachePath, hashKey, lodLevel, mapType)
        if os.path.exists(filePath):
            rotPath = str(blue.rot.FilenameToPath(filePath))
            try:
                cachedTexture = blue.resMan.GetResource(rotPath)
                return cachedTexture
            except:
                sys.exc_clear()



    @bluepy.CCP_STATS_ZONE_METHOD
    def SaveMaps(self, hashKey, lodLevel, maps):
        for (i, each,) in enumerate(maps):
            if (each is not None):
                cachePath = self.GetMapCachePath()
                filePath = self.GetCacheFilePath(cachePath, hashKey, lodLevel, i)
                folder = os.path.split(filePath)[0]
                if not os.path.exists(folder):
                    os.makedirs(folder)
                each.SaveToDDS(filePath)
                each.WaitForSave()




    @bluepy.CCP_STATS_ZONE_METHOD
    def _CreateBiped(self, avatar, geometryRes, name = 'FactoryCreated'):
        biped = trinity.Tr2SkinnedModel()
        biped.skinScale = (1.0, 1.0, 1.0)
        biped.name = name
        if geometryRes:
            biped.geometryResPath = geometryRes
        biped.skeletonName = 'Root'
        avatar.visualModel = biped
        avatar.scaling = (1.0, 1.0, 1.0)
        return avatar



    @bluepy.CCP_STATS_ZONE_METHOD
    def CreateVisualModel(self, gender = PD.GENDER.FEMALE, geometryRes = None):
        biped = trinity.Tr2SkinnedModel()
        if not geometryRes:
            if (gender == PD.GENDER.FEMALE):
                geometryRes = PD.INTERIOR_FEMALE_GEOMETRY_RESPATH
            else:
                geometryRes = PD.INTERIOR_MALE_GEOMETRY_RESPATH
        biped.geometryResPath = geometryRes
        biped.name = gender
        biped.skeletonName = 'Root'
        return biped



    @bluepy.CCP_STATS_ZONE_METHOD
    def CreateAvatar(self, geometryRes, name = 'FactoryCreated'):
        avatar = trinity.WodExtSkinnedObject()
        avatar = self._CreateBiped(avatar, geometryRes, name)
        return avatar



    @bluepy.CCP_STATS_ZONE_METHOD
    def CreateExteriorAvatar(self, geometryRes, name = 'FactoryCreated'):
        return self.CreateAvatar(geometryRes, name)



    @bluepy.CCP_STATS_ZONE_METHOD
    def CreateInteriorAvatar(self, geometryRes, name = 'FactoryCreated'):
        avatar = trinity.Tr2IntSkinnedObject()
        avatar = self._CreateBiped(avatar, geometryRes, name)
        return avatar



    @bluepy.CCP_STATS_ZONE_METHOD
    def RemoveAvatarFromScene(self, avatar, scene):
        if ((avatar is None) or (scene is None)):
            return 
        if (avatar.__typename__.lower() == PD.AVATAR_TYPES.WODEXTSKINNEDOBJECT):
            scene.city.RemoveAvatarFromScene(avatar)
        elif (avatar.__typename__.lower() == PD.AVATAR_TYPES.TR2INTSKINNEDOBJECT):
            try:
                scene.RemoveDynamic(avatar)
            except AttributeError:
                scene.Avatar = None
                sys.exc_clear()
        else:
            msg = ('Avatar passed to Factory::RemoveAvatarFromScene of type %s is neither interior or exterior!' % avatar.__typename__)
            raise AttributeError(msg)



    @bluepy.CCP_STATS_ZONE_METHOD
    def AddAvatarToScene(self, avatar, scene = None):
        if (avatar is None):
            raise AttributeError('No avatar passed to Factory::AddAvatarToScene')
        if (scene is not None):
            if hasattr(scene, 'Avatar'):
                scene.Avatar = avatar
                return 
            try:
                scene.AddDynamic(avatar)
            except AttributeError:
                scene.city.AddAvatarToScene(avatar)
            return 
        typename = avatar.__typename__
        dev = trinity.device
        if dev.scene2:
            if (typename == PD.AVATAR_TYPES.WODEXTSKINNEDOBJECT):
                if ((not hasattr(dev.scene2, 'city')) or (dev.scene2.city is None)):
                    dev.scene2.city = trinity.WodCity()
                dev.scene2.city.AddAvatarToScene(avatar)
            elif (dev.scene2.__typename__ == 'WodScene'):
                raise Exception('Can not add an interior avatar to an exterior scene')
            dev.scene2.AddDynamic(avatar)
        elif (typename == PD.AVATAR_TYPES.WODEXTSKINNEDOBJECT):
            dev.scene2 = trinity.WodScene()
            dev.scene2.city = trinity.WodCity()
            dev.scene2.city.AddAvatarToScene(avatar)
        else:
            dev.scene2 = trinity.Tr2InteriorScene()
            dev.scene2.AddDynamic(avatar)



    @bluepy.CCP_STATS_ZONE_METHOD
    def CreateGWAnimation(self, avatar, morphemeNetwork):
        import GameWorld
        animation = GameWorld.GWAnimation(morphemeNetwork)
        avatar.animationUpdater = animation
        return animation



    @bluepy.CCP_STATS_ZONE_METHOD
    def CreateAnimationOffsets(seld, avatar, doll):
        deg2rad = 57.29577951308232
        if (avatar.animationUpdater and getattr(avatar.animationUpdater, 'network', None)):
            if doll.boneOffsets:
                for bone in doll.boneOffsets:
                    trans = doll.boneOffsets[bone]['translation']
                    avatar.animationUpdater.network.boneOffset.SetOffset(bone, trans[0], trans[1], trans[2])




    @bluepy.CCP_STATS_ZONE_METHOD
    def GetSharedCompositorData(self, gender):
        if PD.USE_PNG:
            suffix = 'png'
        else:
            suffix = 'dds'
        if blue.rot.loadFromContent:
            if not os.path.exists(PD.OUTSOURCING_JESSICA_PATH):
                suffix = 'tga'
        basePath = (self.GetWorkingDirectory(gender) + '/Skin/generic/')
        genderSuffix = gender
        if (gender == PD.GENDER.FEMALE):
            genderSuffix = 'fem'
        return (suffix,
         basePath,
         genderSuffix)



    @bluepy.CCP_STATS_ZONE_METHOD
    def CalcSubRect(self, UV, w, h):
        return (int((w * UV[0])),
         int((h * UV[1])),
         int((w * UV[2])),
         int((h * UV[3])))



    @bluepy.CCP_STATS_ZONE_METHOD
    def CompositeStepsFunction(self, buildDataManager, gender, mapType, partsToComposite, comp, marker, w, h, overrideLod = 0):
        genericHeadMod = self.PreloadedGenericHeadModifiers.get(gender)
        modifierList = [ modifier for modifier in buildDataManager.GetSortedModifiers() if modifier.IsTextureContainingModifier() ]
        if (mapType == PD.DIFFUSE_MAP):
            genTex = genericHeadMod.mapD
        elif (mapType == PD.SPECULAR_MAP):
            genTex = genericHeadMod.mapSRG
        elif (mapType == PD.NORMAL_MAP):
            genTex = genericHeadMod.mapN
        elif (mapType == PD.MASK_MAP):
            genTex = genericHeadMod.mapMask
        baseBodyTexture = genTex.get(PD.DOLL_PARTS.BODY, '')
        baseHeadTexture = genTex.get(PD.DOLL_PARTS.HEAD, '')
        if ((not partsToComposite) or (PD.DOLL_PARTS.BODY in partsToComposite)):
            comp.CopyBlitTexture(baseBodyTexture, self.CalcSubRect(PD.BODY_UVS, w, h))
            self._CompositeTexture(modifierList, PD.DOLL_PARTS.BODY, comp, mapType, w, h, addAlpha=True, overrideLod=overrideLod)
        if ((not partsToComposite) or ((PD.DOLL_PARTS.HEAD in partsToComposite) or (PD.DOLL_PARTS.HAIR in partsToComposite))):
            comp.CopyBlitTexture(baseHeadTexture, self.CalcSubRect(PD.HEAD_UVS, w, h))
            self._CompositeTexture(modifierList, PD.DOLL_PARTS.HEAD, comp, mapType, w, h, addAlpha=False, overrideLod=overrideLod)
        if ((not partsToComposite) or (PD.DOLL_PARTS.HAIR in partsToComposite)):
            self._CompositeTexture(modifierList, PD.DOLL_PARTS.HAIR, comp, mapType, w, h, addAlpha=False, overrideLod=overrideLod)
        if ((not partsToComposite) or (PD.DOLL_PARTS.ACCESSORIES in partsToComposite)):
            self._CompositeTexture(modifierList, PD.DOLL_PARTS.ACCESSORIES, comp, mapType, w, h, addAlpha=False, overrideLod=overrideLod)



    @bluepy.CCP_STATS_ZONE_METHOD
    def CompositeCombinedTexture(self, dollName, mapType, gender, buildDataManager, width, height, compressionSettings = None, overrideLod = 0, mapBundle = None, textureCompositor = None, partsToComposite = None):
        try:
            if (compressionSettings is None):
                compressionSettings = self
            dev = trinity.device
            format = trinity.TRIFMT_A8R8G8B8
            div = PD.MAPSIZERATIOS[mapType]
            w = int((width * div))
            h = int((height * div))
            marker = PD.MAPMARKERS[mapType]
            renderTarget = RTM.GetRenderTarget(format, w, h)
            if (renderTarget.width < w):
                log.LogWarn('PaperDoll:Factory:CompositeCombinedTexture - RenderTarget width less than requested due to low video memory.')
            w = renderTarget.width
            h = renderTarget.height
            if (textureCompositor is None):
                comp = tc.TextureCompositor(renderTarget, targetWidth=w)
            else:
                comp = textureCompositor
            comp.Start(clear=False)
            if mapBundle[mapType]:
                comp.CopyBlitTexture(mapBundle[mapType])
            self.CompositeStepsFunction(buildDataManager, gender, mapType, partsToComposite, comp, marker, w, h, overrideLod=overrideLod)
            comp.End()
            blue.synchro.Yield()
            tex = comp.Finalize(format, w, h, generateMipmap=compressionSettings.generateMipmap)
            comp.renderTarget = None
            if (compressionSettings.compressTextures and compressionSettings.AllowCompress(mapType)):
                AddMarker('compress')
                comptex = dev.CreateTexture(w, h, 1, 0, trinity.TRIFMT_DXT5, trinity.TRIPOOL_MANAGED)
                dev.CompressSurface(tex.GetSurfaceLevel(0), comptex.GetSurfaceLevel(0), (COMPRESS_DXT5n if (mapType is PD.NORMAL_MAP) else COMPRESS_DXT5))
                tex = comptex
                AddMarker('compress done')
            if self.saveTexturesToDisk:
                AddMarker('save')
                cachePath = blue.rot.PathToFilename(('cache:/Avatars/' + dollName))
                if not os.path.exists(cachePath):
                    os.makedirs(cachePath)
                tex.SaveToDDS(((((cachePath + '/') + '/comp') + marker) + '.dds'))
                tex.WaitForSave()
                AddMarker('save done')
            return tex
        except TaskletExit:
            if textureCompositor:
                textureCompositor.renderTarget = None



    @bluepy.CCP_STATS_ZONE_METHOD
    def _CompositeDiffuseMap(self, modifierList, bodyPart, textureCompositor, width, height, addAlpha = False, overrideLod = 0):
        for m in [ x for x in iter(modifierList) if (x.weight > 0.0) ]:
            blue.pyos.BeNice()
            if (m.categorie == PD.DOLL_PARTS.HEAD):
                continue
            if ((m.stubblePath != '') and (overrideLod < 0)):
                continue
            UVs = m.GetUVsForCompositing(bodyPart)
            subrect = self.CalcSubRect(UVs, width, height)
            srcRect = None
            texture = m.mapD
            skipAlpha = False
            useAlphaTest = False
            if (m.colorize and (bodyPart in m.mapL)):
                if (bodyPart not in m.mapO):
                    m.mapO[bodyPart] = 'res:/texture/global/stub.dds'
                if (bodyPart not in m.mapZ):
                    m.mapZ[bodyPart] = 'res:/texture/global/stub.dds'
                if (m.categorie in (PD.BODY_CATEGORIES.SKINTONE,
                 PD.HEAD_CATEGORIES.MAKEUP,
                 PD.BODY_CATEGORIES.TATTOO,
                 PD.BODY_CATEGORIES.SCARS,
                 PD.DOLL_EXTRA_PARTS.BODYSHAPES,
                 PD.HEAD_CATEGORIES.FACEMODIFIERS)):
                    if ('implants/' in m.name):
                        skipAlpha = False
                        addAlpha = True
                    elif (('eyelashes/' in m.name) or ('eyebrows/' in m.name)):
                        skipAlpha = False
                        addAlpha = True
                        useAlphaTest = True
                    else:
                        skipAlpha = True
                        addAlpha = True
                if ((m.categorie == PD.DOLL_PARTS.HAIR) and (bodyPart == PD.DOLL_PARTS.HEAD)):
                    skipAlpha = False
                    addAlpha = True
                if (m.pattern == ''):
                    colors = m.colorizeData
                    if (bodyPart == PD.DOLL_PARTS.HAIR):
                        textureCompositor.ColorizedCopyBlitTexture(m.mapL[bodyPart], m.mapZ[bodyPart], m.mapO[bodyPart], colors[0], colors[1], colors[2], subrect)
                    else:
                        textureCompositor.ColorizedBlitTexture(m.mapL[bodyPart], m.mapZ[bodyPart], m.mapO[bodyPart], colors[0], colors[1], colors[2], subrect=subrect, addAlpha=addAlpha, skipAlpha=skipAlpha, useAlphaTest=useAlphaTest, weight=m.weight)
                else:
                    colors = m.patternData
                    patternDir = self.GetPatternDir()
                    patternPath = (((patternDir + '/') + m.pattern) + '_z.dds')
                    if blue.rot.loadFromContent:
                        if not os.path.exists(PD.OUTSOURCING_JESSICA_PATH):
                            patternPath = patternPath.replace('.dds', '.tga')
                    textureCompositor.PatternBlitTexture(patternPath, m.mapL[bodyPart], m.mapZ[bodyPart], m.mapO[bodyPart], colors[0], colors[1], colors[2], colors[3], colors[4], subrect, patternTransform=colors[5], patternRotation=colors[6], addAlpha=addAlpha, skipAlpha=skipAlpha)
            if ((bodyPart in texture) and (not (m.colorize and (m.categorie == PD.BODY_CATEGORIES.TATTOO)))):
                maskname = tname = texture[bodyPart]
                if not ((type(tname) in types.StringTypes) and ('/skin/generic/' in tname.lower())):
                    if (bodyPart in texture):
                        maskname = texture[bodyPart]
                    elif (bodyPart in m.mapL):
                        maskname = m.mapL[bodyPart]
                    skipAlpha = False
                    if (m.categorie in (PD.BODY_CATEGORIES.SKINTONE,
                     PD.HEAD_CATEGORIES.MAKEUP,
                     PD.BODY_CATEGORIES.TATTOO,
                     PD.BODY_CATEGORIES.SCARS,
                     PD.DOLL_EXTRA_PARTS.BODYSHAPES,
                     PD.DOLL_EXTRA_PARTS.ARCHETYPES,
                     PD.HEAD_CATEGORIES.FACEMODIFIERS)):
                        if ('eyelashes/' in m.name):
                            skipAlpha = False
                            addAlpha = False
                        else:
                            skipAlpha = True
                            addAlpha = True
                    if ((m.categorie == PD.DOLL_PARTS.HAIR) and (bodyPart == PD.DOLL_PARTS.HEAD)):
                        skipAlpha = False
                        addAlpha = True
                    if (bodyPart == PD.DOLL_PARTS.HAIR):
                        textureCompositor.CopyBlitTexture(tname, subrect=subrect, srcRect=srcRect)
                    else:
                        textureCompositor.BlitTexture(tname, maskname, m.weight, subrect=subrect, addAlpha=addAlpha, skipAlpha=skipAlpha, srcRect=srcRect, multAlpha=False)




    @bluepy.CCP_STATS_ZONE_METHOD
    def _CompositeSpecularMap(self, modifierList, bodyPart, textureCompositor, width, height, addAlpha = False, overrideLod = 0):
        for m in [ x for x in iter(modifierList) if (x.weight > 0.0) ]:
            blue.pyos.BeNice()
            if (m.categorie == PD.DOLL_PARTS.HEAD):
                continue
            if ((m.stubblePath != '') and (overrideLod < 0)):
                continue
            UVs = m.GetUVsForCompositing(bodyPart)
            subrect = self.CalcSubRect(UVs, width, height)
            srcRect = None
            skipAlpha = False
            useAlphaTest = False
            maskname = tname = m.mapSRG.get(bodyPart)
            if ((bodyPart in m.mapSRG) and (not ((m.categorie == PD.BODY_CATEGORIES.TATTOO) or ((type(tname) in types.StringTypes) and ('/skin/generic/' in tname.lower()))))):
                if (bodyPart in m.mapD):
                    maskname = m.mapD[bodyPart]
                elif (bodyPart in m.mapL):
                    maskname = m.mapL[bodyPart]
                skipAlpha = True
                addAlpha = True
                if m.useSkin:
                    tname = tname.replace('_S.', '_SR.')
                doColorizedStep = False
                if (m.colorize and m.specularColorData):
                    doColorizedStep = (bodyPart in m.mapL)
                    if (bodyPart == PD.DOLL_PARTS.HAIR):
                        textureCompositor.CopyBlitTexture(tname, subrect=subrect, srcRect=srcRect)
                        doColorizedStep = True
                    if doColorizedStep:
                        if (bodyPart not in m.mapO):
                            m.mapO[bodyPart] = 'res:/texture/global/stub.dds'
                        if (bodyPart not in m.mapZ):
                            m.mapZ[bodyPart] = 'res:/texture/global/stub.dds'
                        colors = m.specularColorData
                        textureCompositor.ColorizedBlitTexture(m.mapSRG[bodyPart], m.mapZ[bodyPart], m.mapO[bodyPart], colors[0], colors[1], colors[2], mask=maskname, subrect=subrect, addAlpha=addAlpha, skipAlpha=skipAlpha, weight=m.weight)
                if not doColorizedStep:
                    textureCompositor.BlitTexture(tname, maskname, m.weight, subrect=subrect, addAlpha=addAlpha, skipAlpha=skipAlpha, srcRect=srcRect, multAlpha=False)
                if (m.colorize and (m.specularColorData and (bodyPart in m.mapZ))):
                    values = (m.specularColorData[0][3],
                     m.specularColorData[1][3],
                     m.specularColorData[2][3],
                     1.0)
                    textureCompositor.BlitAlphaIntoAlphaWithMaskAndZones(tname, maskname, m.mapZ[bodyPart], values, subrect=subrect, srcRect=srcRect)
                else:
                    textureCompositor.BlitAlphaIntoAlphaWithMask(tname, maskname, subrect=subrect, srcRect=srcRect)




    @bluepy.CCP_STATS_ZONE_METHOD
    def _CompositeNormalMap(self, modifierList, bodyPart, textureCompositor, width, height, addAlpha = False, overrideLod = 0):
        for m in [ x for x in iter(modifierList) if (x.weight > 0.0) ]:
            blue.pyos.BeNice()
            if (m.categorie == PD.DOLL_PARTS.HEAD):
                continue
            if ((m.stubblePath != '') and (overrideLod < 0)):
                continue
            texture = m.mapN
            UVs = m.GetUVsForCompositing(bodyPart)
            subrect = self.CalcSubRect(UVs, width, height)
            srcRect = None
            skipAlpha = False
            useAlphaTest = False
            if (bodyPart in texture):
                maskname = tname = texture[bodyPart]
                if not ((type(tname) in types.StringTypes) and ('/skin/generic/' in tname.lower())):
                    if (bodyPart in m.mapD):
                        maskname = m.mapD[bodyPart]
                    elif (bodyPart in m.mapL):
                        maskname = m.mapL[bodyPart]
                    skipAlpha = False
                    if (m.categorie in (PD.BODY_CATEGORIES.SKINTONE,
                     PD.HEAD_CATEGORIES.MAKEUP,
                     PD.BODY_CATEGORIES.TATTOO,
                     PD.BODY_CATEGORIES.SCARS,
                     PD.DOLL_EXTRA_PARTS.BODYSHAPES,
                     PD.DOLL_EXTRA_PARTS.ARCHETYPES,
                     PD.HEAD_CATEGORIES.FACEMODIFIERS)):
                        skipAlpha = True
                        addAlpha = True
                    elif ((m.categorie == PD.DOLL_PARTS.HAIR) and (bodyPart == PD.DOLL_PARTS.HEAD)):
                        skipAlpha = False
                        addAlpha = True
                    if (bodyPart == PD.DOLL_PARTS.HAIR):
                        textureCompositor.CopyBlitTexture(tname, subrect=subrect, srcRect=srcRect)
                    else:
                        textureCompositor.BlitTexture(tname, maskname, m.weight, subrect=subrect, addAlpha=addAlpha, skipAlpha=skipAlpha, srcRect=srcRect, multAlpha=False)
            mapD = m.mapD.get(bodyPart)
            mapMN = m.mapMN.get(bodyPart)
            mapTN = m.mapTN.get(bodyPart)
            if mapMN:
                textureCompositor.MaskedNormalBlitTexture(mapMN, m.weight, subrect, srcRect=srcRect)
            if mapTN:
                textureCompositor.TwistNormalBlitTexture(mapTN, m.weight, subrect, srcRect=srcRect)
            if (mapD and (m.categorie not in (PD.BODY_CATEGORIES.SKINTONE,
             PD.BODY_CATEGORIES.TATTOO,
             PD.BODY_CATEGORIES.SCARS,
             PD.DOLL_EXTRA_PARTS.BODYSHAPES,
             PD.DOLL_EXTRA_PARTS.ARCHETYPES,
             PD.HEAD_CATEGORIES.FACEMODIFIERS))):
                if (m.categorie in PD.CATEGORIES_THATCLEAN_MATERIALMAP):
                    textureCompositor.SubtractAlphaFromAlpha(mapD, subrect, srcRect=srcRect)
                mapMM = m.mapMaterial.get(bodyPart)
                if (mapMM is not None):
                    textureCompositor.BlitTextureIntoAlphaWithMask(mapMM, mapD, subrect, srcRect=srcRect)
                else:
                    textureCompositor.BlitTextureIntoAlphaWithMask(mapD, mapD, subrect, srcRect=srcRect)




    @bluepy.CCP_STATS_ZONE_METHOD
    def _CompositeMaskMap(self, modifierList, bodyPart, textureCompositor, width, height, addAlpha = False, overrideLod = 0):
        for m in [ x for x in iter(modifierList) if (x.weight > 0.0) ]:
            blue.pyos.BeNice()
            if (m.categorie == PD.DOLL_PARTS.HEAD):
                continue
            if ((m.stubblePath != '') and (overrideLod < 0)):
                continue
            texture = m.mapMask
            UVs = m.GetUVsForCompositing(bodyPart)
            subrect = self.CalcSubRect(UVs, width, height)
            srcRect = None
            skipAlpha = False
            useAlphaTest = False
            if (bodyPart in texture):
                tname = texture[bodyPart].lower()
                textureCompositor.CopyBlitTexture(tname, subrect)
                maskname = tname = texture[bodyPart]
                if not ((type(tname) in types.StringTypes) and ('/skin/generic/' in tname.lower())):
                    if (bodyPart in m.mapD):
                        maskname = m.mapD[bodyPart]
                    elif (bodyPart in m.mapL):
                        maskname = m.mapL[bodyPart]
                    skipAlpha = False
                    if (m.categorie in (PD.BODY_CATEGORIES.SKINTONE,
                     PD.HEAD_CATEGORIES.MAKEUP,
                     PD.BODY_CATEGORIES.TATTOO,
                     PD.BODY_CATEGORIES.SCARS,
                     PD.DOLL_EXTRA_PARTS.BODYSHAPES,
                     PD.DOLL_EXTRA_PARTS.ARCHETYPES,
                     PD.HEAD_CATEGORIES.FACEMODIFIERS)):
                        skipAlpha = True
                        addAlpha = True
                    if ((m.categorie == PD.DOLL_PARTS.HAIR) and (bodyPart == PD.DOLL_PARTS.HEAD)):
                        skipAlpha = False
                        addAlpha = True
                    textureCompositor.BlitTexture(tname, maskname, m.weight, subrect=subrect, addAlpha=addAlpha, skipAlpha=skipAlpha, srcRect=srcRect, multAlpha=False)




    @bluepy.CCP_STATS_ZONE_METHOD
    def _CompositeTexture(self, modifierList, bodyPart, textureCompositor, textureType, width, height, addAlpha = False, overrideLod = 0):
        if (textureType == PD.DIFFUSE_MAP):
            return self._CompositeDiffuseMap(modifierList, bodyPart, textureCompositor, width, height, addAlpha, overrideLod)
        if (textureType == PD.SPECULAR_MAP):
            return self._CompositeSpecularMap(modifierList, bodyPart, textureCompositor, width, height, addAlpha, overrideLod)
        if (textureType == PD.NORMAL_MAP):
            return self._CompositeNormalMap(modifierList, bodyPart, textureCompositor, width, height, addAlpha, overrideLod)
        if (textureType == PD.MASK_MAP):
            return self._CompositeMaskMap(modifierList, bodyPart, textureCompositor, width, height, addAlpha, overrideLod)
        raise ValueError('Paperdoll::Unsupported texturetype passed to compositing')



    @staticmethod
    @bluepy.CCP_STATS_ZONE_FUNCTION
    def BindCompositeTexturesToMeshes(meshes, mapBundle):

        def SetResourceTexture(effectResource, texture):
            if isinstance(texture, str):
                if (effectResource.resourcePath != texture):
                    effectResource.resourcePath = texture
            else:
                effectResource.resourcePath = ''
                effectResource.SetResource(texture)


        for each in iter(meshes):
            effects = PD.GetEffectsFromMesh(each)
            for f in iter(effects):
                if (type(f) != trinity.Tr2ShaderMaterial):
                    for r in iter(f.resources):
                        if (mapBundle.diffuseMap and (r.name == PD.MAPNAMES[PD.DIFFUSE_MAP])):
                            SetResourceTexture(r, mapBundle.diffuseMap)
                        elif (mapBundle.specularMap and (r.name == PD.MAPNAMES[PD.SPECULAR_MAP])):
                            SetResourceTexture(r, mapBundle.specularMap)
                        elif (mapBundle.normalMap and (r.name == PD.MAPNAMES[PD.NORMAL_MAP])):
                            SetResourceTexture(r, mapBundle.normalMap)
                        else:
                            if (mapBundle.maskMap and (r.name == PD.MAPNAMES[PD.MASK_MAP])):
                                SetResourceTexture(r, mapBundle.maskMap)

                else:
                    if mapBundle.diffuseMap:
                        f.parameters[PD.MAPNAMES[PD.DIFFUSE_MAP]] = mapBundle.diffuseMap
                    if mapBundle.specularMap:
                        f.parameters[PD.MAPNAMES[PD.SPECULAR_MAP]] = mapBundle.specularMap
                    if mapBundle.normalMap:
                        f.parameters[PD.MAPNAMES[PD.NORMAL_MAP]] = mapBundle.normalMap
                    if mapBundle.maskMap:
                        f.parameters[PD.MAPNAMES[PD.MASK_MAP]] = mapBundle.maskMap





    @bluepy.CCP_STATS_ZONE_METHOD
    def BuildMeshForAvatar(self, avatar, avatarName, buildSources, createDynamicLOD = False, bodyShapesActive = {}, overrideLod = 0, weldthreshold = 1e-09, meshName = 'mesh', addMesh = False, asyncLOD = False):
        builder = trinity.WodAvatar2Builder()
        avatarName = str(avatarName)
        if asyncLOD:
            builder.createGPUMesh = True
            builder.effectPath = 'res:/Graphics/Effect/Managed/Interior/Avatar/SkinnedAvatarBRDFDouble.fx'
            if (overrideLod == 2):
                weldthreshold = 0
        for each in iter(buildSources):
            builder.sourceMeshesInfo.append(each)

        realBsNames = {}
        realBsNames['fatshape'] = 'FatShape'
        realBsNames['thinshape'] = 'ThinShape'
        realBsNames['muscularshape'] = 'MuscularShape'
        realBsNames['softshape'] = 'SoftShape'
        if bodyShapesActive:
            for bs in bodyShapesActive:
                for suffix in ['',
                 '1',
                 '2',
                 '3',
                 '4',
                 '5',
                 '6']:
                    blendshape1 = trinity.WodAvatar2BuilderBlend()
                    if (bs in realBsNames):
                        blendshape1.name = (realBsNames[bs] + suffix)
                    else:
                        blendshape1.name = (bs + suffix)
                    blendshape1.power = bodyShapesActive[bs]
                    builder.blendshapeInfo.append(blendshape1)


        cachePath = blue.rot.PathToFilename(('cache:/Avatars/' + avatarName))
        if not os.path.exists(cachePath):
            os.makedirs(cachePath)
        builder.outputName = str((((('cache:/Avatars/' + avatarName) + '/') + meshName) + '.gr2'))
        builder.weldThreshold = weldthreshold

        class BuildEvent():

            def __init__(self):
                self.isDone = False
                self.succeeded = False



            def __call__(self, success):
                self.isDone = True
                self.succeeded = success



            def Wait(self):
                while not self.isDone:
                    blue.pyos.synchro.Yield()





        def BuildSkinnedModel(avatarBuilder, lod):
            avatarBuilder.lod = lod
            avatarBuilder.PrepareForBuild()
            evt = BuildEvent()
            avatarBuilder.BuildAsync(evt)
            evt.Wait()
            if not evt.succeeded:
                return 
            else:
                ret = avatarBuilder.GetSkinnedModel()
                return ret


        if asyncLOD:
            return BuildSkinnedModel(builder, overrideLod)



    @bluepy.CCP_STATS_ZONE_METHOD
    def RemoveMeshesFromVisualModel(self, visualModel, ignoreMeshes = None):
        if ignoreMeshes:
            remIdxs = []
            vmMeshCount = len(visualModel.meshes)
            for i in xrange(vmMeshCount):
                if (visualModel.meshes[i] not in ignoreMeshes):
                    remIdxs.insert(0, i)

            for i in remIdxs:
                del visualModel.meshes[i]

        else:
            del visualModel.meshes[:]



    @bluepy.CCP_STATS_ZONE_METHOD
    def AppendMeshesToVisualModel(self, visualModel, meshes):
        for mesh in iter(meshes):
            if ((type(mesh) is trinity.Tr2Mesh) and (mesh not in visualModel.meshes)):
                visualModel.meshes.append(mesh)




    @bluepy.CCP_STATS_ZONE_METHOD
    def RemoveMeshesFromVisualModelByCategory(self, visualModel, category):
        remList = []
        for each in iter(visualModel.meshes):
            if each.name.startswith(category):
                remList.append(each)

        for each in iter(remList):
            visualModel.meshes.remove(each)




    @bluepy.CCP_STATS_ZONE_METHOD
    def ReplaceMeshesOnAvatar(self, visualModel, meshes):
        remList = []
        for each in iter(meshes):
            for every in iter(visualModel.meshes):
                if (every.name == each.name):
                    remList.append(every)


        for each in iter(remList):
            visualModel.meshes.remove(each)

        self.AppendMeshesToVisualModel(visualModel, meshes)



    @bluepy.CCP_STATS_ZONE_METHOD
    def GetDNAFromYamlFile(self, filename):
        resFile = blue.ResFile()
        if resFile.FileExists(filename):
            resFile.Open(filename)
            yamlStr = resFile.Read()
            resFile.Close()
            if (len(yamlStr) == 0):
                raise Exception('PaperDoll::Factory::GetDNAFromYamlFile - Error, loading an empty file!')
            dna = PD.NastyYamlLoad(yamlStr)
            return dna
        raise Exception(('No %s file found' % filename))




class RedundantUpdateException(Exception):
    __guid__ = 'paperDoll.RedundantUpdateException'


class KillUpdateException(Exception):
    __guid__ = 'paperDoll.KillUpdateException'


class Doll(object):
    __metaclass__ = bluepy.CCP_STATS_ZONE_PER_METHOD
    __guid__ = 'paperDoll.Doll'
    _Doll__nextInstanceID = -1

    @staticmethod
    def GetInstanceID():
        Doll._Doll__nextInstanceID += 1
        return Doll._Doll__nextInstanceID



    @staticmethod
    def InstanceEquals(dollA, dollB):
        return (dollA.instanceID == dollB.instanceID)



    def __init__(self, name, gender = PD.GENDER.FEMALE):
        object.__init__(self)
        self.instanceID = Doll.GetInstanceID()
        self.compressionSettings = None
        self._Doll__quitRequested = False
        self.name = name
        self.buildDataManager = PD.BuildDataManager()
        self.UVs = {PD.DOLL_PARTS.HEAD: PD.DEFAULT_UVS,
         PD.DOLL_PARTS.BODY: PD.DEFAULT_UVS,
         PD.DOLL_PARTS.HAIR: PD.DEFAULT_UVS,
         PD.DOLL_PARTS.ACCESSORIES: PD.DEFAULT_UVS}
        self.morphsLoaded = False
        self.isOptimized = False
        self._Doll__textureResolution = [2048, 1024]
        self._Doll__overrideLod = 0
        self.usePrepass = False
        self.avatarType = ''
        self.gender = gender
        self.busyBaking = False
        self.busyLoadingDNA = False
        self.busyHandlingBlendshapes = False
        self.lastUpdateRedundant = False
        self.hairColorizeData = []
        self.boneOffsets = {}
        self.bindPose = None
        self.mapBundle = MapBundle()
        self.skinLightmapRenderer = None
        self.modifierLimits = {}
        self.textureCompositor = None
        self._Doll__updateFrameStamp = trinity.GetCurrentFrameCounter()
        self._Doll__useFastShader = False
        self._currentUpdateTasklet = None
        self._UpdateTaskletChildren = []
        self.decalBaker = None
        trinity.device.RegisterResource(self)
        self.deviceResetAvatar = None
        self.useMaskedShaders = False
        self.useHeroHead = False
        self.forceHair = ''
        self.hiddenCategories = []
        self.useDXT5N = False
        self.blendShapeMeshCache = {}
        self.lodsInSeparateFolder = False
        if hasattr(const, 'PAPERDOLL_LODS_IN_SEPARATE_FOLDER'):
            self.lodsInSeparateFolder = const.PAPERDOLL_LODS_IN_SEPARATE_FOLDER



    def __del__(self):
        self.StopPaperDolling()
        self.skinLightmapRenderer = None



    def GetName(self):
        return self.name



    def GetProjectedDecals(self):
        modifiers = self.buildDataManager.GetModifiersByCategory(PD.BODY_CATEGORIES.TATTOO)
        modifiers = [ modifier for modifier in modifiers if modifier.decalData ]
        return modifiers



    def AddBlendshapeLimitsFromFile(self, resPath):
        data = PD.ModifierLoader.LoadBlendshapeLimits(resPath)
        if (data and (data.get('gender') == self.gender)):
            limits = data['limits']
            self.AddModifierLimits(limits)



    def AddModifierLimits(self, modifierLimits):
        self.modifierLimits.update(modifierLimits)



    def OnInvalidate(self, level):
        self.blendShapeMeshCache = {}
        if (self.skinLightmapRenderer is not None):
            self.skinLightmapRenderer.StopRendering()
            self.deviceResetAvatar = self.skinLightmapRenderer.skinnedObject
            del self.skinLightmapRenderer
            self.skinLightmapRenderer = None



    def OnCreate(self, dev):
        if (self._Doll__overrideLod <= PD.LOD_SCATTER_SKIN):
            if (self.skinLightmapRenderer is None):
                self.skinLightmapRenderer = PD.SkinLightmapRenderer(useOptix=(self._Doll__overrideLod == PD.LOD_RAYTRACE))
            self.skinLightmapRenderer.SetSkinnedObject(self.deviceResetAvatar)
            self.skinLightmapRenderer.StartRendering()
            self.deviceResetAvatar = None



    def StopPaperDolling(self):
        for each in self.buildDataManager.GetModifiersAsList():
            each.ClearCachedData()

        self.mapBundle.ReCreate()



    def SetUsePrepass(self, val = True):
        self.usePrepass = val



    def IsPrepass(self):
        if self.usePrepass:
            return True
        else:
            return False



    def IsInterior(self):
        if (self.avatarType != ''):
            if (self.avatarType == 'interior'):
                return True
            else:
                return False
        elif trinity.device.scene2:
            if (trinity.device.scene2.__typename__ == 'WodScene'):
                return False
            if (trinity.device.scene2.__typename__ == 'Tr2InteriorScene'):
                return True
        return False



    def GetMorphsFromGr2(self, gr2Path):
        names = {}
        resMan = blue.resMan
        gr2Res = resMan.GetResource(gr2Path, 'raw')
        while gr2Res.isLoading:
            blue.synchro.Yield()

        meshCount = gr2Res.GetMeshCount()
        for i in xrange(meshCount):
            morphCount = gr2Res.GetMeshMorphCount(i)
            for j in xrange(morphCount):
                name = gr2Res.GetMeshMorphName(i, j)
                data = names.get(i, [])
                data.append(name)
                names[i] = data


        return (gr2Res, names)



    def ConfigureMeshForMaskedShader(self, mesh, remove = False):
        decalToOpaque = []
        fx = PD.GetEffectsFromAreaList(mesh.opaqueAreas)
        for f in iter(fx):
            for p in f.parameters:
                if (p.name == 'CutMaskInfluence'):
                    p.value = 0.85


        for decalArea in mesh.decalAreas:
            f = decalArea.effect
            for p in f.parameters:
                if (p.name == 'CutMaskInfluence'):
                    if remove:
                        if (abs((p.value - 0.85)) < 0.001):
                            decalToOpaque.append(decalArea)
                        p.value = 0.0
                    else:
                        if (abs((p.value - 0.85)) > 0.001):
                            p.value = 1.01


        if remove:
            for area in decalToOpaque:
                mesh.opaqueAreas.append(area)
                mesh.decalAreas.remove(area)

        else:
            PD.MoveAreas(mesh.opaqueAreas, mesh.decalAreas)



    def __AnalyzeBuildDataForRules(self, updateRuleBundle, buildDataManager, dirtyModifiers):
        modifierList = buildDataManager.GetSortedModifiers()
        forcesLooseTop = False
        hidesBootShin = False
        forceHair = ''
        useMaskedShaders = False
        hiddenCategories = set(self.hiddenCategories)
        for modifier in iter(modifierList):
            useMaskedShaders = (useMaskedShaders or ((modifier.categorie == PD.BODY_CATEGORIES.OUTER) and (len(modifier.mapMask) > 0)))
            metaData = modifier.metaData
            if metaData:
                if (metaData.forcesHairType and (metaData.forcesHairType not in ('', 'none'))):
                    forceHair = metaData.forcesHairType
                forcesLooseTop = (forcesLooseTop or metaData.forcesLooseTop)
                hidesBootShin = (hidesBootShin or metaData.hidesBootShin)
                if metaData.hidesTypes:
                    for ht in iter(metaData.hidesTypes):
                        hiddenCategories.add(ht)


        if ((forceHair != self.forceHair) or (PD.DOLL_PARTS.HAIR in hiddenCategories)):
            updateRuleBundle.rebuildHair = True
        if ((not useMaskedShaders) and self.useMaskedShaders):
            updateRuleBundle.undoMaskedShaders = True
        if hidesBootShin:
            buildDataManager.ChangeDesiredOrder(PD.BODY_CATEGORIES.FEET, PD.BODY_CATEGORIES.BOTTOMOUTER)
        else:
            buildDataManager.ChangeDesiredOrder(PD.BODY_CATEGORIES.BOTTOMOUTER, PD.BODY_CATEGORIES.FEET)
        if forcesLooseTop:
            buildDataManager.ChangeDesiredOrder(PD.BODY_CATEGORIES.TOPMIDDLE, PD.BODY_CATEGORIES.BOTTOMOUTER)
        else:
            buildDataManager.ChangeDesiredOrder(PD.BODY_CATEGORIES.BOTTOMOUTER, PD.BODY_CATEGORIES.TOPMIDDLE)
        self.forceHair = forceHair
        self.useMaskedShaders = useMaskedShaders
        self.useHeroHead = (self.overrideLod == PD.LOD_HERO)
        self.hiddenCategories = list(hiddenCategories)
        updateRuleBundle.forcesLooseTop = forcesLooseTop
        updateRuleBundle.hidesBootShin = hidesBootShin



    def GetLODPathsToReplace(self):
        nonLodFolder = '/modular/'
        lodFolder = '/modularlod/'
        if PD.GENDER_ROOT:
            nonLodFolder = '/paperdoll/'
            lodFolder = '/paperdoll_lod/'
        return (nonLodFolder, lodFolder)



    def AdjustGr2PathForLod(self, gr2Path):
        (nonLodFolder, lodFolder,) = self.GetLODPathsToReplace()
        lodInPath = (('_lod' in gr2Path.lower()) and (not gr2Path.split('.')[0].lower().endswith('0')))
        lodsInSeparateFolder = self.lodsInSeparateFolder
        if (self.overrideLod > 0):
            if lodInPath:
                gr2Path = ((gr2Path.split('.')[0][:-1] + str(self.overrideLod)) + '.gr2')
            else:
                gr2Path = gr2Path.replace('.gr2', (('_lod' + str(self.overrideLod)) + '.gr2'))
            if lodsInSeparateFolder:
                gr2Path = gr2Path.lower().replace(nonLodFolder, lodFolder)
        elif (self.overrideLod == 0):
            overridePath = ''
            if lodInPath:
                overridePath = (gr2Path.lower().replace(nonLodFolder, lodFolder).split('.')[0][:-1] + 'a.gr2')
            else:
                overridePath = gr2Path.lower().replace(nonLodFolder, lodFolder).replace('.gr2', '_loda.gr2')
            file = blue.ResFile()
            if file.FileExists(overridePath):
                gr2Path = overridePath
            else:
                if lodInPath:
                    gr2Path = (gr2Path.split('.')[0][:-5] + '.gr2')
                if lodsInSeparateFolder:
                    gr2Path = gr2Path.lower().replace(lodFolder, nonLodFolder)
        elif lodInPath:
            gr2Path = (gr2Path.split('.')[0][:-5] + '.gr2')
        if lodsInSeparateFolder:
            gr2Path = gr2Path.lower().replace(lodFolder, nonLodFolder)
        return gr2Path



    def __AdjustMeshForLod(self, mesh):
        mesh.geometryResPath = self.AdjustGr2PathForLod(mesh.geometryResPath)



    def AdjustRedFileForLod(self, redfile, clothPath = ''):
        (nonLodFolder, lodFolder,) = self.GetLODPathsToReplace()
        if not PerformanceOptions.useLodForRedfiles:
            return redfile
        else:
            redfile = redfile.lower()
            lodInPath = (('_lod' in redfile) and (not redfile.split('.')[0].endswith('0')))
            lodsInSeparateFolder = self.lodsInSeparateFolder
            if (self.overrideLod > 0):
                if lodInPath:
                    redfile = ((redfile.split('.')[0][:-1] + str(self.overrideLod)) + '.red')
                else:
                    redfile = redfile.replace('.red', (('_lod' + str(self.overrideLod)) + '.red'))
                if lodsInSeparateFolder:
                    redfile = redfile.replace(nonLodFolder, lodFolder)
                file = blue.ResFile()
                if ((not file.FileExists(redfile)) and (clothPath != '')):
                    redfile = redfile.replace('_physx', '').replace('_nosim', '')
            elif (self.overrideLod == 0):
                overridePath = ''
                if lodInPath:
                    overridePath = (redfile.replace(nonLodFolder, lodFolder).split('.')[0][:-1] + 'a.red')
                else:
                    overridePath = redfile.replace(nonLodFolder, lodFolder).replace('.red', '_loda.red')
                file = blue.ResFile()
                if file.FileExists(overridePath):
                    redfile = overridePath
                else:
                    if (clothPath != ''):
                        redfile = clothPath.lower().replace('_physx', '_physx_nosim')
                    if lodInPath:
                        redfile = (redfile.split('.')[0][:-5] + '.red')
                    if lodsInSeparateFolder:
                        redfile = redfile.replace(lodFolder, nonLodFolder)
            elif (clothPath != ''):
                redfile = clothPath.lower().replace('_physx', '_physx_nosim')
            if lodInPath:
                redfile = (redfile.split('.')[0][:-5] + '.red')
            if lodsInSeparateFolder:
                redfile = redfile.replace(lodFolder, nonLodFolder)
            return redfile



    def __ProcessRules(self, buildDataManager, modifierList, visualModel, updateRuleBundle):
        if updateRuleBundle.rebuildHair:
            hairModifiers = [ x for x in buildDataManager.GetHairModifiers() if (x not in modifierList) ]
            modifierList.extend(hairModifiers)
        loadedObjects = {}
        modifiersWithDataToLoad = [ modifier for modifier in modifierList if modifier.redfile ]
        modLen = len(modifiersWithDataToLoad)
        remIdx = []

        def StripDigits(name):
            return ''.join((letter.lower() for letter in name if not letter.isdigit()))



        def PutMeshToLookup(lookup, m):
            meshName = StripDigits(m.name)
            c = 0
            try:
                c = int(m.name.split(meshName)[-1])
            except ValueError:
                c = 0
            meshName = StripDigits(m.name)
            lookup[meshName] = (max([c, lookup.get(meshName)]) + 1)


        lookupMeshes = {}
        if visualModel:
            for m in iter(visualModel.meshes):
                PutMeshToLookup(lookupMeshes, m)

        for i in xrange(modLen):
            modifier = modifiersWithDataToLoad[i]
            if (modifier.redfile not in loadedObjects):
                redfile = modifier.redfile
                if ((modifier.categorie == PD.DOLL_PARTS.HAIR) and (self.forceHair != '')):
                    redfile = modifier.redfile.replace('.red', (('_' + self.forceHair) + '.red'))
                elif ((modifier.categorie == PD.DOLL_PARTS.HEAD) and self.useHeroHead):
                    redfile = modifier.redfile.replace('_generic', '_detailed')
                redfileLod = self.AdjustRedFileForLod(redfile, modifier.clothPath)
                if ((not modifier.meshes) or (redfileLod != modifier.redfile)):
                    object = blue.os.LoadObject(redfileLod)
                    if (object is None):
                        object = blue.os.LoadObject(redfile)
                    loadedObjects[modifier.redfile] = object
                    blue.pyos.BeNice()
                else:
                    remIdx.insert(0, i)

        for i in iter(remIdx):
            del modifiersWithDataToLoad[i]

        for modifier in iter(modifiersWithDataToLoad):
            modifier.ClearCachedData()
            categorie = modifier.categorie
            if (categorie in self.hiddenCategories):
                continue
            redfile = modifier.redfile
            if (redfile and (len(redfile) > 0)):
                newBodyPart = loadedObjects.get(redfile, None)
                if newBodyPart:
                    remList = self.GetMeshesToRemove(modifier, updateRuleBundle)
                    if hasattr(newBodyPart, 'meshes'):
                        for (i, mesh,) in enumerate(newBodyPart.meshes):
                            if (categorie == PD.DOLL_PARTS.HAIR):
                                if (mesh.name == 'HairMeshShape'):
                                    PD.MoveAreas(mesh.opaqueAreas, mesh.transparentAreas)
                                    PD.MoveAreas(mesh.decalAreas, mesh.transparentAreas)
                                else:
                                    PD.MoveAreas(mesh.opaqueAreas, mesh.decalAreas)
                                    PD.MoveAreas(mesh.transparentAreas, mesh.decalAreas)
                            if (mesh.name in ('DrapeShape', 'TuckShape')):
                                PD.MoveAreas(mesh.opaqueAreas, mesh.transparentAreas)
                            if (mesh.name not in remList):
                                self._Doll__AdjustMeshForLod(mesh)
                                scat = str(categorie)
                                if (scat in lookupMeshes):
                                    num = lookupMeshes[scat]
                                    mesh.name = (scat + str(num))
                                else:
                                    mesh.name = scat
                                PutMeshToLookup(lookupMeshes, mesh)
                                if self.blendShapeMeshCache.get(mesh.name):
                                    del self.blendShapeMeshCache[mesh.name]
                                modifier.meshes.append(mesh)
                                modifier.meshGeometryResPaths[mesh.name] = mesh.geometryResPath


        loadedObjects.clear()
        self.ApplyBoneOffsets(buildDataManager.GetModifiersAsList())



    def __CalculateAccessoryUVs(self):

        def PackTextures(textures, target):
            watermark = 0
            returnUVs = []

            def BoxCoversX(x, box):
                if ((x >= box[0]) and (x < box[2])):
                    return True
                else:
                    return False



            def GetHeightAt(x):
                height = 0
                newX = x
                for r in returnUVs:
                    if BoxCoversX(x, r):
                        if (r[3] > height):
                            height = r[3]
                            newX = r[2]

                return (height, newX)



            def BoxIntersects(x, y, box, uv):
                for vert in ((x, y),
                 ((x + box[0]), y),
                 (x, (y + box[1])),
                 ((x + box[0]), (y + box[1]))):
                    if ((vert[0] > uv[0]) and ((vert[0] < uv[2]) and ((vert[1] > uv[1]) and (vert[1] < uv[3])))):
                        return True




            def BoxFits(x, y, box):
                if ((x < 0) or (((x + box[0]) > target[0]) or (y < 0))):
                    return False
                else:
                    for r in returnUVs:
                        if BoxIntersects(x, y, box, r):
                            return False

                    return True


            for t in textures:
                candidates = {}
                minInc = 100000
                x = 0.0
                while (x < 1.0):
                    currentX = x
                    (h, x,) = GetHeightAt(currentX)
                    inc = (h + t[1])
                    if BoxFits(currentX, h, t):
                        if (inc < minInc):
                            minInc = inc
                            candidates[inc] = (currentX, h)
                    if (x == currentX):
                        x += (1 / 512.0)

                if len(candidates):
                    final = candidates[minInc]
                    returnUVs.append((final[0],
                     final[1],
                     (final[0] + t[0]),
                     (final[1] + t[1])))
                    watermark = (final[1] + t[1])

            if (watermark > target[1]):
                factor = (target[1] / watermark)
                for ix in range(len(returnUVs)):
                    returnUVs[ix] = [(returnUVs[ix][0] * factor),
                     (returnUVs[ix][1] * factor),
                     (returnUVs[ix][2] * factor),
                     (returnUVs[ix][3] * factor)]

            return returnUVs



        def GetMapSize(path):
            param = trinity.TriTexture2DParameter()
            param.resourcePath = path
            while param.resource.isLoading:
                blue.synchro.Yield()

            return (param.resource.width, param.resource.height)


        accesorieModifiers = self.buildDataManager.GetModifiersByPart(PD.DOLL_PARTS.ACCESSORIES)
        numAcc = len(accesorieModifiers)
        targetBox = (1.0, 1.0)
        textures = []
        for acc in iter(accesorieModifiers):
            tag = 'accessories'
            map = ''
            if (tag in acc.mapD):
                map = acc.mapD[tag]
            if (tag in acc.mapL):
                map = acc.mapL[tag]
            size = (0.0, 0.0)
            adjustedSize = (0.0, 0.0)
            if (map != ''):
                size = GetMapSize(map)
                adjustedSize = [(size[0] / 512.0), (size[1] / 512.0)]
                if (adjustedSize[0] > 0.5):
                    adjustedSize[0] = 0.5
                if (adjustedSize[1] > 0.5):
                    adjustedSize[1] = 0.5
            textures.append(adjustedSize)

        result = PackTextures(textures, targetBox)
        for ix in range(len(accesorieModifiers)):
            mod = accesorieModifiers[ix]
            mod.ulUVs = (result[ix][0], result[ix][1])
            mod.lrUVs = (result[ix][2], result[ix][3])




    def __CalculateAccessorieUVs(self):
        useProperAlgorithm = True
        if useProperAlgorithm:
            self._Doll__CalculateAccessoryUVs()
            return 
        accesorieModifiers = self.buildDataManager.GetModifiersByPart(PD.DOLL_PARTS.ACCESSORIES)
        numAcc = len(accesorieModifiers)
        if (numAcc > 0):
            split = 1
            if (numAcc == 1):
                split = 1
            elif (numAcc < 5):
                split = 2
            elif (numAcc < 10):
                split = 3
            elif (numAcc < 17):
                split = 4
            else:
                split = 5
                if (numAcc > 25):
                    numAcc = 25
                    accesorieModifiers = accesorieModifiers[0:25]
            uvs = []
            for x in xrange(split):
                for y in xrange(split):
                    w = (1.0 / split)
                    uv = [(x * w),
                     (y * w),
                     ((x + 1) * w),
                     ((y + 1) * w)]
                    uvs.append(uv)


            uvIndex = 0
            for acc in iter(accesorieModifiers):
                (ula, ulb, lra, lrb,) = uvs[uvIndex]
                acc.ulUVs = (ula, ulb)
                acc.lrUVs = (lra, lrb)
                uvIndex += 1




    def __ApplyUVs(self):
        self._Doll__CalculateAccessorieUVs()
        UVs = {PD.DOLL_PARTS.HEAD: PD.HEAD_UVS,
         PD.DOLL_PARTS.BODY: PD.BODY_UVS,
         PD.DOLL_PARTS.HAIR: PD.HAIR_UVS,
         PD.DOLL_PARTS.ACCESSORIES: PD.ACCE_UVS}
        modifiersList = self.buildDataManager.GetSortedModifiers()
        for modifier in iter(modifiersList):
            meshes = list(modifier.meshes)
            if modifier.clothData:
                meshes.append(modifier.clothData)
            for mesh in iter(meshes):
                uvCategory = self.buildDataManager.CategoryToPart(modifier.categorie)
                if (uvCategory == PD.DOLL_PARTS.ACCESSORIES):
                    for area in mesh.decalAreas:
                        if ('stubble' in area.name.lower()):
                            uvCategory = PD.DOLL_PARTS.HEAD
                            break

                for category in UVs:
                    if (hasattr(mesh, 'opaqueAreas') and (hasattr(mesh, 'decalAreas') and hasattr(mesh, 'transparentAreas'))):
                        for area in ((tuple(mesh.opaqueAreas) + tuple(mesh.decalAreas)) + tuple(mesh.transparentAreas)):
                            if ((category.lower() + 'uv') in area.name.lower()):
                                uvCategory = category
                                break


                fxs = PD.GetEffectsFromMesh(mesh)
                for fx in iter(fxs):
                    if ((type(fx) != trinity.Tr2ShaderMaterial) and (fx.effectResource and fx.effectResource.isLoading)):
                        while fx.effectResource.IsLoading():
                            blue.synchro.Yield()

                    transformUV0 = None
                    for effectParameter in iter(fx.parameters):
                        if (effectParameter.name == 'TransformUV0'):
                            transformUV0 = effectParameter
                            break

                    if not transformUV0:
                        transformUV0 = trinity.Tr2Vector4Parameter()
                        transformUV0.name = 'TransformUV0'
                        fx.parameters.append(transformUV0)
                    computedUVs = None
                    if (uvCategory == PD.DOLL_PARTS.ACCESSORIES):
                        width = (UVs[uvCategory][2] - UVs[uvCategory][0])
                        height = (UVs[uvCategory][3] - UVs[uvCategory][1])
                        computedUVs = ((UVs[uvCategory][0] + (modifier.ulUVs[0] * width)),
                         (UVs[uvCategory][1] + (modifier.ulUVs[1] * height)),
                         (UVs[uvCategory][0] + (modifier.lrUVs[0] * width)),
                         (UVs[uvCategory][1] + (modifier.lrUVs[1] * height)))
                    elif (type(mesh) is trinity.Tr2ClothingActor):
                        computedUVs = (UVs[uvCategory][0],
                         UVs[uvCategory][3],
                         UVs[uvCategory][2],
                         UVs[uvCategory][1])
                    else:
                        computedUVs = (UVs[uvCategory][0],
                         UVs[uvCategory][1],
                         UVs[uvCategory][2],
                         UVs[uvCategory][3])
                    transformUV0.value = computedUVs






    def GetMorphTargets(self):
        modifierList = self.buildDataManager.GetSortedModifiers()
        removedModifiers = self.buildDataManager.GetDirtyModifiers(removedBit=True)
        morphTargets = {}

        def Qualifier(modifier):
            return ((modifier.categorie in PD.BLENDSHAPE_CATEGORIES) and (modifier.weight != 0.0))


        for modifier in iter(removedModifiers):
            if Qualifier(modifier):
                morphTargets[modifier.name] = 0

        for modifier in iter(modifierList):
            if Qualifier(modifier):
                morphTargets[modifier.name] = modifier.weight

        return morphTargets



    def __HandleBlendShapes(self, removedModifiers):
        self.SpawnUpdateChildTasklet(self._Doll__HandleBlendShapes_t, removedModifiers)



    def __HandleBlendShapes_t(self, removedModifiers):
        try:
            self.busyHandlingBlendshapes = True
            modifierList = self.buildDataManager.GetSortedModifiers()
            meshGeometryResPaths = {}
            for modifier in iter(modifierList):
                meshGeometryResPaths.update(modifier.meshGeometryResPaths)

            morphTargets = self.GetMorphTargets()
            if morphTargets:
                meshes = self.buildDataManager.GetMeshes()
                Factory.ApplyMorphTargetsToMeshes(meshes, morphTargets, self.blendShapeMeshCache, meshGeometryResPaths)
                for mesh in meshes:
                    if (mesh.name not in self.blendShapeMeshCache):
                        mesh.deferGeometryLoad = False

            for mesh in self.buildDataManager.GetMeshes(alternativeModifierList=removedModifiers):
                if self.blendShapeMeshCache.get(mesh.name):
                    del self.blendShapeMeshCache[mesh.name]


        finally:
            self.busyHandlingBlendshapes = False




    def ApplyBoneOffsets(self, modifierList = None):
        modifierList = (modifierList or self.buildDataManager.GetSortedModifiers())
        self.boneOffsets = {}
        modifiersDone = []
        for modifier in iter(modifierList):
            if (modifier.poseData != None):
                if (modifier.respath not in modifiersDone):
                    modifiersDone.append(modifier.respath)
                    weight = modifier.weight
                    for bone in iter(modifier.poseData):
                        if (bone not in self.boneOffsets):
                            newT = modifier.poseData[bone]['translation']
                            newR = modifier.poseData[bone]['rotation']
                            self.boneOffsets[bone] = {}
                            self.boneOffsets[bone]['translation'] = ((newT[0] * weight),
                             (newT[1] * weight),
                             (newT[2] * weight))
                            self.boneOffsets[bone]['rotation'] = ((newR[0] * weight),
                             (newR[1] * weight),
                             (newR[2] * weight))
                        else:
                            prevT = self.boneOffsets[bone]['translation']
                            prevR = self.boneOffsets[bone]['rotation']
                            newT = modifier.poseData[bone]['translation']
                            newR = modifier.poseData[bone]['rotation']
                            self.boneOffsets[bone]['translation'] = ((prevT[0] + (newT[0] * weight)),
                             (prevT[1] + (newT[1] * weight)),
                             (prevT[2] + (newT[2] * weight)))
                            self.boneOffsets[bone]['rotation'] = ((prevR[0] + (newR[0] * weight)),
                             (prevR[1] + (newR[1] * weight)),
                             (prevR[2] + (newR[2] * weight)))





    def GetMeshesToRemove(self, modifier, updateRuleBundle):
        meshes = []
        categorie = modifier.categorie
        if (modifier.metaData is not None):
            if (modifier.metaData.usesTucking == True):
                if modifier.tuck:
                    meshes = ['DrapeShape']
                else:
                    meshes = ['TuckShape']
        if (categorie == PD.BODY_CATEGORIES.TOPINNER):
            if updateRuleBundle.forcesLooseTop:
                meshes = ['Tight_TopShape', 'TightShape']
            else:
                meshes = ['Loose_TopShape', 'LooseShape']
        if (self.overrideLod >= 0):
            meshes.append('Detail_LooseShape')
            meshes.append('Detail_LooseshapeShape')
        elif ((categorie == PD.BODY_CATEGORIES.FEET) and updateRuleBundle.hidesBootShin):
            meshes = ['ShinShape']
        return meshes



    def __EnsureCompleteBody(self, factory):
        options = factory.GetOptionsByGender(self.gender)

        def BodyPartSearch(modifier, categoryStartsWith):
            return (modifier.IsMeshContainingModifier() and modifier.categorie.startswith(categoryStartsWith))



        def EnsureBodyPart(bodyModifiers, categoryStartsWith, defaultPart):
            if not any(map(lambda x: BodyPartSearch(x, categoryStartsWith), bodyModifiers)):
                self.buildDataManager.AddModifier(factory.CollectBuildData(defaultPart, options, 1.0), privilegedCaller=True)


        headModifier = self.GetBuildDataByResPath(PD.DEFAULT_NUDE_HEAD)
        if not headModifier:
            self.buildDataManager.AddModifier(factory.CollectBuildData(PD.DEFAULT_NUDE_HEAD, options, 1.0), privilegedCaller=True)
        makeupModifiers = self.buildDataManager.GetModifiersByCategory(PD.HEAD_CATEGORIES.MAKEUP)
        foundEyelashes = False
        for makeup in makeupModifiers:
            if makeup.name.startswith('eyelashes'):
                foundEyelashes = True

        if not foundEyelashes:
            self.buildDataManager.AddModifier(factory.CollectBuildData(PD.DEFAULT_NUDE_EYELASHES, options, 1.0), privilegedCaller=True)
        bodyModifiers = self.buildDataManager.GetModifiersByPart(PD.DOLL_PARTS.BODY)
        EnsureBodyPart(bodyModifiers, 'bottom', PD.DEFAULT_NUDE_LEGS)
        EnsureBodyPart(bodyModifiers, PD.BODY_CATEGORIES.FEET, PD.DEFAULT_NUDE_FEET)
        EnsureBodyPart(bodyModifiers, PD.BODY_CATEGORIES.HANDS, PD.DEFAULT_NUDE_HANDS)
        EnsureBodyPart(bodyModifiers, 'top', PD.DEFAULT_NUDE_TORSO)



    def Load(self, filename, factory):
        self.LoadYaml(filename, factory)



    def LoadYaml(self, filename, factory):
        dna = factory.GetDNAFromYamlFile(filename)
        self.LoadDNA(dna, factory)



    def CreateModifierFromFootPrint(self, factory, footPrint):

        def doDecal(modifier):
            decal = footPrint.get(PD.DNA_STRINGS.DECALDATA)
            if decal:
                modifier.decalData = PD.ProjectedDecal.Load(decal)


        pathlower = footPrint[PD.DNA_STRINGS.PATH].lower()
        if (pathlower not in factory.GetOptionsByGender(self.gender)):
            log.LogWarn("Paperdoll - Doll::CreateModifierFromFootPrint is creating modifier that doesn't exist in options.")
            modifier = PD.BuildData(pathlower)
            modifier.weight = float(footPrint[PD.DNA_STRINGS.WEIGHT])
            doDecal(modifier)
        else:
            modifier = factory.CollectBuildData(pathlower, factory.GetOptionsByGender(self.gender))
            modifier.weight = footPrint[PD.DNA_STRINGS.WEIGHT]
            modifier.pattern = footPrint.get(PD.DNA_STRINGS.PATTERN, '')
            modifier.tuck = footPrint.get(PD.DNA_STRINGS.TUCK, False)
            if (type(footPrint.get(PD.DNA_STRINGS.COLORS)) in types.StringTypes):
                colorData = eval(footPrint.get(PD.DNA_STRINGS.COLORS))
            else:
                colorData = footPrint.get(PD.DNA_STRINGS.COLORS)
            if colorData:
                if modifier.pattern:
                    modifier.patternData = colorData
                    if (len(modifier.patternData) == 5):
                        modifier.patternData.append((0, 0, 8, 8))
                        modifier.patternData.append(0.0)
                else:
                    modifier.colorizeData = colorData
            if (PD.DNA_STRINGS.SPECULARCOLORS in footPrint):
                modifier.specularColorData = footPrint.get(PD.DNA_STRINGS.SPECULARCOLORS)
            if footPrint.get(PD.DNA_STRINGS.COLORVARIATION, ''):
                modifier.SetColorVariation(footPrint[PD.DNA_STRINGS.COLORVARIATION])
            if footPrint.get(PD.DNA_STRINGS.VARIATION, ''):
                modifier.SetVariation(footPrint[PD.DNA_STRINGS.VARIATION])
            doDecal(modifier)
        return modifier



    def LoadDNA(self, data, factory, tattoos = None, buildDataManager = None):
        self.busyLoadingDNA = True
        if (data[0] in PD.GENDER):
            self.gender = data[0]
            data = data[1:]
        buildDataManager = (buildDataManager or self.buildDataManager)
        for footPrint in iter(data):
            modifier = self.CreateModifierFromFootPrint(factory, footPrint)
            buildDataManager.AddModifier(modifier)

        self.busyLoadingDNA = False



    def GetDNA(self, preserveTypes = False, getHiddenModifiers = False, getWeightless = False):
        dna = [self.gender]
        modifiers = self.buildDataManager.GetSortedModifiers(showHidden=getHiddenModifiers)
        for each in iter(modifiers):
            if ((each.weight > 0.0) or getWeightless):
                data = each.GetFootPrint(preserveTypes)
                dna.append(data)

        return dna



    def MatchDNA(self, factory, dna):
        if (dna[0] in PD.GENDER):
            self.gender = dna[0]
            dna = dna[1:]
        remList = []
        tempDNA = list(dna)
        for modifier in self.buildDataManager.GetSortedModifiers():
            found = False
            for footPrint in tempDNA:
                if modifier.CompareFootPrint(footPrint):
                    tempDNA.remove(footPrint)
                    found = True
                    break

            if found:
                continue
            remList.append(modifier)
            continue

        for modifier in remList:
            self.buildDataManager.RemoveModifier(modifier)

        for footPrint in tempDNA:
            modifier = self.CreateModifierFromFootPrint(factory, footPrint)
            self.buildDataManager.AddModifier(modifier)




    def Save(self, filename):
        f = file(filename, 'w')
        dnaData = self.GetDNA()
        yaml.dump(dnaData, f, default_flow_style=False)
        f.close()



    def GetBuildDataManager(self):
        return self.buildDataManager



    def GetBuildDataByCategory(self, category):
        modifiers = self.buildDataManager.GetModifiersByCategory(category)
        if ((not modifiers) and (category in (PD.DOLL_PARTS + PD.DOLL_EXTRA_PARTS))):
            modifiers = self.buildDataManager.GetModifiersByPart(category)
        return modifiers



    def GetBuildDataByResPath(self, resPath, includeFuture = False):
        for bd in self.buildDataManager.GetSortedModifiers(includeFuture=includeFuture):
            if (bd.respath == resPath):
                return bd




    def _AddResource(self, res, weight, factory, colorization = None, variation = None, colorVariation = None, privilegedCaller = False, rawColorVariation = None):
        parts = res.split(PD.SEPERATOR_CHAR)
        if (len(parts) == 1):
            raise Exception('Can not add a whole category to the character')
        options = factory.GetOptionsByGender(self.gender)
        modifier = factory.CollectBuildData(res, options, weight)
        categorie = modifier.categorie
        if (categorie in [PD.DOLL_PARTS.HAIR]):
            if (categorie == PD.DOLL_PARTS.HAIR):
                hairModifiers = self.buildDataManager.GetModifiersByCategory(categorie)
                if hairModifiers:
                    modifier.colorizeData = hairModifiers[0].colorizeData
                else:
                    if self.hairColorizeData:
                        modifier.colorizeData = self.hairColorizeData
            self.ApplyVariationsToModifier(modifier, colorization, variation, colorVariation, rawColorVariation=rawColorVariation)
            self.buildDataManager.SetModifiersByCategory(categorie, modifier, privilegedCaller=privilegedCaller)
        else:
            self.ApplyVariationsToModifier(modifier, colorization, variation, colorVariation, rawColorVariation=rawColorVariation)
            self.buildDataManager.AddModifier(modifier, privilegedCaller=privilegedCaller)
        return modifier



    def AddItemType(self, factory, itemType, weight = 1.0, rawColorVariation = None):
        if (type(itemType) is not tuple):
            itemType = factory.GetItemType(itemType)
        if (type(itemType) is tuple):
            return self.AddResource(itemType[0], weight, factory, variation=itemType[1], colorVariation=itemType[2], rawColorVariation=rawColorVariation)



    def ApplyVariationsToModifier(self, modifier, colorization = None, variation = None, colorVariation = None, rawColorVariation = None):
        if colorization:
            modifier.SetColorizeData(colorization)
        if variation:
            modifier.SetVariation(variation)
        if colorVariation:
            modifier.SetColorVariation(colorVariation)
        if rawColorVariation:
            if colorVariation:
                log.LogWarn('paperDoll::Doll:: ApplyVariationsToModifier: applying both colorVariation and rawColorVariation')
            modifier.SetColorVariationDirectly(rawColorVariation)



    def AddResource(self, res, weight, factory, colorization = None, variation = None, colorVariation = None, privilegedCaller = False, rawColorVariation = None):
        modifier = None
        if (type(res) is PD.ProjectedDecal):
            decalName = (res.label or 'Decal {0}'.format(res.layer))
            modifier = PD.BuildData(name=decalName, categorie=PD.BODY_CATEGORIES.TATTOO)
            modifier.decalData = res
            self.ApplyVariationsToModifier(modifier, colorization, variation, colorVariation)
            self.buildDataManager.AddModifier(modifier, privilegedCaller=privilegedCaller)
        else:
            modifier = self._AddResource(res, weight, factory, colorization=colorization, variation=variation, colorVariation=colorVariation, privilegedCaller=privilegedCaller, rawColorVariation=rawColorVariation)
        return modifier



    def AddResources(self, resList, weight, factory):
        for each in iter(resList):
            self.AddResource(each, weight, factory)




    def RemoveResource(self, res, factory, privilegedCaller = False):
        parts = res.split(PD.SEPERATOR_CHAR)
        if (len(parts) == 1):
            raise Exception('Can not remove a whole category from the character')
        categorie = str(parts[0].lower())
        modifiers = self.buildDataManager.GetModifiersByCategory(categorie, showHidden=True)
        targetFun = lambda modifier: (str(modifier.respath.lower()) == str(res.lower()))
        target = None
        for modifier in modifiers:
            if targetFun(modifier):
                target = modifier
                break

        if target:
            self.buildDataManager.RemoveModifier(target, privilegedCaller=privilegedCaller)
        return target



    def __HandleDependantModifiers(self, factory):
        currentModifiers = self.buildDataManager.GetSortedModifiers()
        removedModifiers = self.buildDataManager.GetDirtyModifiers(removedBit=True)
        changedModifiers = self.buildDataManager.GetDirtyModifiers(changedBit=True)

        def GetDependantModifiersRes(modifiers, filterCurrent):
            dependantModifiers = set()
            dependantModifiersFullData = set()
            for modifier in iter(modifiers):
                if modifier.metaData:
                    if modifier.metaData.dependantModifiers:
                        for dependantModifier in iter(modifier.metaData.dependantModifiers):
                            modifierPath = dependantModifier.lower()
                            dependantModifiersFullData.add(modifierPath)
                            if ('#' in modifierPath):
                                modifierPath = modifierPath.split('#')[0]
                            dependantModifiers.add(modifierPath)


            if filterCurrent:
                currentModifiersResPath = set([ modifier.respath.lower() for modifier in currentModifiers ])
                removedModifiersResPath = set([ modifier.respath.lower() for modifier in removedModifiers ])
                dependantModifiers = (dependantModifiers - (currentModifiersResPath - removedModifiersResPath))
                deleteList = []
                for entry in dependantModifiersFullData:
                    path = entry.split('#')[0]
                    if (path not in dependantModifiers):
                        deleteList.append(entry)

                for d in deleteList:
                    dependantModifiersFullData.remove(d)

            return (list(dependantModifiers), list(dependantModifiersFullData))


        (dependantModifiersToRemove, dummy,) = GetDependantModifiersRes(removedModifiers, filterCurrent=False)
        for modifier in iter(changedModifiers):
            metaDataOld = modifier.GetVariationMetaData(modifier.lastVariation)
            metaDataNew = modifier.GetVariationMetaData(modifier.currentVariation)
            if (metaDataOld and metaDataNew):
                sN = set(metaDataNew.dependantModifiers)
                sO = set(metaDataOld.dependantModifiers)
                for dm in sO.difference(sN):
                    dependantModifiersToRemove.append(dm)


        for resPath in iter(dependantModifiersToRemove):
            if (resPath != PD.DEFAULT_NUDE_HEAD):
                self.RemoveResource(resPath, factory, privilegedCaller=True)

        (dependantModifiersToAdd, dependantModifiersFullData,) = GetDependantModifiersRes(currentModifiers, filterCurrent=True)
        for data in iter(dependantModifiersFullData):
            if ('#' in data):
                parts = data.split('#')
                if (len(parts) >= 4):
                    path = parts[0]
                    var = None
                    if (parts[2] != ''):
                        var = parts[2]
                    cVar = None
                    if (parts[1] != ''):
                        cVar = parts[1]
                    self.AddResource(parts[0], float(parts[3]), factory, variation=var, colorVariation=cVar, privilegedCaller=True)
                else:
                    self.AddResource(parts[0], 1.0, factory, privilegedCaller=True)
            else:
                self.AddResource(data, 1.0, factory, privilegedCaller=True)




    def QueryModifiers(self, modifierList, fun):
        return all(map(fun, modifierList))



    def __CheckQuitRequested(self):
        if self._Doll__quitRequested:
            raise KillUpdateException('Preemptively killing Update')



    def SpawnUpdateChildTasklet(self, fun, *args):
        self._UpdateTaskletChildren.append(uthread.new(fun, *args))



    def ReapUpdateChildTasklets(self):
        for t in self._UpdateTaskletChildren:
            t.kill()

        del self._UpdateTaskletChildren[:]



    def Update(self, factory, avatar = None, visualModel = None, LODMode = False):
        if self._Doll__quitRequested:
            return 
        startNewTasklet = True
        if (self._Doll__updateFrameStamp == trinity.GetCurrentFrameCounter()):
            log.LogWarn('paperDoll::Doll::Update is being called more than once on the same frame for the same doll instance! Fix your code so you only call Update after all needed changes have been applied.')
        elif (self._currentUpdateTasklet and (self._Doll__updateFrameStamp < trinity.GetCurrentFrameCounter())):
            if self._currentUpdateTasklet.alive:
                self._Doll__quitRequested = True
                self._currentUpdateTasklet.raise_exception(KillUpdateException, 'Preemptively killing Update')
                while self._currentUpdateTasklet:
                    blue.pyos.BeNice()

            else:
                startNewTasklet = False
        if startNewTasklet:
            self._Doll__quitRequested = False
            self._Doll__updateFrameStamp = trinity.GetCurrentFrameCounter()
            self._currentUpdateTasklet = uthread.new(self.Update_t, *(factory,
             avatar,
             visualModel,
             LODMode))
            self._currentUpdateTasklet.context = 'paperDoll::Doll::Update'
            uthread.schedule(self._currentUpdateTasklet)



    def IsBusyUpdating(self):
        return (self._currentUpdateTasklet and self._currentUpdateTasklet.alive)



    def Update_t(self, factory, avatar, visualModel, LODMode):
        try:
            try:
                while self.busyLoadingDNA:
                    blue.synchro.Yield()

                buildDataManager = self.buildDataManager
                buildDataManager.Lock()
                updateRuleBundle = UpdateRuleBundle()
                blue.pyos.BeNice()
                if (avatar and (hasattr(avatar, 'visualModel') and (not visualModel))):
                    visualModel = avatar.visualModel
                if LODMode:
                    buildDataManager.SetAllAsDirty()
                AddMarker('Update Start')
                PD.FullOptixRenderer.Pause()
                self._Doll__EnsureCompleteBody(factory)
                hdm = self._Doll__HandleDependantModifiers
                for i in xrange(3):
                    hdm(factory)

                gdm = buildDataManager.GetDirtyModifiers
                addedModifiers = gdm(addedBit=True)
                removedModifiers = gdm(removedBit=True)
                changedModifiers = gdm(changedBit=True)
                dirtyModifiers = list(set(((removedModifiers + addedModifiers) + changedModifiers)))
                if not dirtyModifiers:
                    raise RedundantUpdateException('Warning - Call made to PaperDoll.Update() when no modifier is dirty!')
                addedAndChangedModifiers = list(set((addedModifiers + changedModifiers)))
                updateRuleBundle.SetBlendShapesOnly(self.QueryModifiers(dirtyModifiers, fun=lambda x: x.IsBlendshapeModifier()))
                updateRuleBundle.SetDecalsOnly(self.QueryModifiers(dirtyModifiers, fun=lambda x: (x.decalData is not None)))
                if ((not updateRuleBundle.blendShapesOnly) and (avatar and (self.overrideLod < 2))):
                    dirtyDecalModifiers = [ decalModifier for decalModifier in self.buildDataManager.GetModifiersByCategory(PD.BODY_CATEGORIES.TATTOO) if decalModifier.decalData ]
                    updateRuleBundle.doDecals = ((len(dirtyDecalModifiers) > 0) and ((not updateRuleBundle.blendShapesOnly) and (avatar and (self.overrideLod < 2))))
                if not (updateRuleBundle.blendShapesOnly or updateRuleBundle.decalsOnly):
                    self._Doll__AnalyzeBuildDataForRules(updateRuleBundle, buildDataManager, dirtyModifiers)
                    self._Doll__ProcessRules(buildDataManager, addedAndChangedModifiers, visualModel, updateRuleBundle)
                    updateRuleBundle.meshesAddedOrRemoved = (not (updateRuleBundle.blendShapesOnly or updateRuleBundle.decalsOnly))
                wrinkleFx = []
                hashStubble = ''
                if ((not updateRuleBundle.blendShapesOnly) and avatar):
                    factory.CreateAnimationOffsets(avatar, self)
                if ((not updateRuleBundle.decalsOnly) and self.modifierLimits):
                    for modifier in iter(addedAndChangedModifiers):
                        limit = self.modifierLimits.get(modifier.name)
                        if limit:
                            if (modifier.weight > limit[1]):
                                modifier.weight = limit[1]
                            else:
                                if (modifier.weight < limit[0]):
                                    modifier.weight = limit[0]

                if (updateRuleBundle.blendShapesOnly or updateRuleBundle.doDecals):
                    if not updateRuleBundle.decalsOnly:
                        self._Doll__HandleBlendShapes(removedModifiers)
                    if updateRuleBundle.doDecals:
                        self.BakeDecals_t(factory, dirtyDecalModifiers)
                clothMeshes = []
                if ((not (updateRuleBundle.decalsOnly or updateRuleBundle.blendShapesOnly)) and avatar):
                    clothMeshes = self.LoadClothData(addedAndChangedModifiers)
                hashStubble = PD.PortraitTools.GetStubbleHash(addedAndChangedModifiers)
                if updateRuleBundle.meshesAddedOrRemoved:
                    self._Doll__ApplyUVs()
                if visualModel:
                    updateRuleBundle.partsToComposite = buildDataManager.GetPartsFromMaps(dirtyModifiers)
                    if (self.overrideLod > 1):
                        updateRuleBundle.mapsToComposite = [PD.DIFFUSE_MAP]
                    elif self.mapBundle.AllMapsGood():
                        updateRuleBundle.mapsToComposite = buildDataManager.GetMapsToComposite(dirtyModifiers)
                    else:
                        updateRuleBundle.partsToComposite = None
                    blendShapeVector = [ modifier.weight for modifier in buildDataManager.GetSortedModifiers() if (modifier.categorie in PD.BLENDSHAPE_CATEGORIES) ]
                    hES = (self._Doll__textureResolution,
                     self.compressionSettings,
                     hashStubble,
                     blendShapeVector)
                    hashKey = buildDataManager.HashForMaps(hashableElements=hES)
                    self.CompositeTextures(factory, hashKey, updateRuleBundle)
                    while any(map(lambda x: x.alive, self._UpdateTaskletChildren)):
                        blue.synchro.Yield()

                meshes = self.buildDataManager.GetMeshes(includeClothMeshes=True)
                meshes.sort(key=lambda x: getattr(x, 'name', '___'))
                if not (updateRuleBundle.blendShapesOnly or updateRuleBundle.doDecals):
                    self._Doll__HandleBlendShapes(removedModifiers)
                Factory.BindCompositeTexturesToMeshes(meshes, self.mapBundle)
                if updateRuleBundle.meshesAddedOrRemoved:
                    self.ConfigureMaskedShader(updateRuleBundle)
                    self.ApplyShaders(meshes, wrinkleFx, factory)
                    factory.BindGlobalMaps(meshes)
                    if visualModel:
                        factory.RemoveMeshesFromVisualModel(visualModel)
                    if (avatar and (avatar.clothMeshes is not None)):
                        if (len(avatar.clothMeshes) > 0):
                            del avatar.clothMeshes[:]
                    if (avatar and (avatar.clothMeshes is not None)):
                        for mesh in iter(meshes):
                            if (type(mesh) is trinity.Tr2ClothingActor):
                                avatar.clothMeshes.append(mesh)

                    if visualModel:
                        factory.AppendMeshesToVisualModel(visualModel, meshes)
                if avatar:
                    avatar.ResetAnimationBindings()
                if (visualModel and ((self.overrideLod < 0) and (not (updateRuleBundle.decalsOnly or updateRuleBundle.blendShapesOnly)))):
                    PD.PortraitTools.HandleRemovedStubble(removedModifiers, buildDataManager)
                    PD.PortraitTools.HandleUpdatedStubble(addedAndChangedModifiers, addedModifiers, buildDataManager)
                if ((not (updateRuleBundle.decalsOnly or updateRuleBundle.blendShapesOnly)) and (len(wrinkleFx) > 0)):
                    self.LoadBindPose()
                    newSet = PD.PortraitTools().SetupWrinkleMapControls(avatar, wrinkleFx, self)
                if (visualModel and (avatar and self.IsPrepass())):
                    PD.prePassFixup.AddPrepassAreasToAvatar(avatar, visualModel, self)
                if visualModel:
                    visualModel.ResetAnimationBindings()
                if avatar:
                    freq = PerformanceOptions.updateFreq.get(self.overrideLod, 0)
                    if (freq == 0):
                        avatar.updatePeriod = 0
                    else:
                        avatar.updatePeriod = (1.0 / freq)
                if ((self._Doll__overrideLod <= PD.LOD_SCATTER_SKIN) and (self.skinLightmapRenderer is None)):
                    self.skinLightmapRenderer = PD.SkinLightmapRenderer(useOptix=(self._Doll__overrideLod == PD.LOD_RAYTRACE))
                    self.skinLightmapRenderer.SetSkinnedObject(avatar)
                    self.skinLightmapRenderer.StartRendering()
                if (visualModel and (avatar and (not LODMode))):
                    if (self.skinLightmapRenderer is not None):
                        self.skinLightmapRenderer.SetSkinnedObject(avatar)
                    buildDataManager.NotifyUpdate()
                    self.lastUpdateRedundant = False
                    PD.FullOptixRenderer.NotifyUpdate()
                while any(map(lambda x: x.alive, self._UpdateTaskletChildren)):
                    blue.synchro.Yield()

            except RedundantUpdateException, err:
                self.lastUpdateRedundant = True
                sys.exc_clear()
            except KillUpdateException, err:
                self.lastUpdateRedundant = True
                self.ReapUpdateChildTasklets()
                sys.exc_clear()
            except Exception, err:
                log.LogException(str(err))
                sys.exc_clear()

        finally:
            self.buildDataManager.UnLock()
            self.ReapUpdateChildTasklets()
            AddMarker('End Start')
            self._currentUpdateTasklet = None




    def ConfigureMaskedShader(self, updateRuleBundle):
        maskingCategories = (PD.BODY_CATEGORIES.HANDS,
         PD.BODY_CATEGORIES.BOTTOMINNER,
         PD.BODY_CATEGORIES.BOTTOMOUTER,
         'dependants')
        allMods = self.buildDataManager.GetSortedModifiers()
        if (self.useMaskedShaders or updateRuleBundle.undoMaskedShaders):
            for m in iter(allMods):
                if (m.categorie.startswith('top') or (m.categorie in maskingCategories)):
                    for mesh in iter(m.meshes):
                        self.ConfigureMeshForMaskedShader(mesh, remove=updateRuleBundle.undoMaskedShaders)





    def BakeDecals_t(self, factory, decalModifiers):
        if decalModifiers:
            if (self.decalBaker is None):
                self.decalBaker = PD.DecalBaker(factory)
            while self.busyHandlingBlendshapes:
                blue.synchro.Yield()

            self.decalBaker.CreateTargetAvatarFromDoll(self)
            self.decalBaker.SetSize(self.textureResolution)
            for decalModifier in decalModifiers:
                self.decalBaker.BakeDecalToModifier(decalModifier.decalData, decalModifier)
                while not self.decalBaker.isReady:
                    blue.synchro.Yield()

                decalModifier.mapL.update(decalModifier.mapD)
                decalModifier.colorize = True




    def LogDNA(self, factory):
        if factory.verbose:
            lines = yaml.dump(self.GetDNA(), default_flow_style=False).splitlines()
            log.LogInfo(('Building paperDoll: ' + self.name))
            for each in iter(lines):
                log.LogInfo(each)




    def CompositeTextures(self, factory, hashKey, updateRuleBundle):
        disableCaching = updateRuleBundle.blendShapesOnly
        lastCompositedTexturesOK = self.mapBundle.AllMapsGood()
        didComposite = False
        if ((not disableCaching) and factory.allowTextureCache):
            textureCache = []
            for i in xrange(len(PD.MAPS)):
                tex = factory.FindCachedTexture(hashKey, self.overrideLod, PD.MAPS[i])
                textureCache.insert((i + 1), tex)

            while any(map(lambda x: ((x is not None) and x.isLoading), textureCache)):
                blue.synchro.Yield()

            for i in xrange(len(textureCache)):
                if (textureCache[i] and textureCache[i].isGood):
                    self.mapBundle.SetMapByTypeIndex(i, textureCache[i], hashKey)

        if (self.compressionSettings is not None):
            self.compressionSettings.generateMipmap = False
        for mapTypeIndex in updateRuleBundle.mapsToComposite:
            if (hashKey == self.mapBundle.hashKeys.get(mapTypeIndex)):
                continue

            def CompositeTasklet_t(mapTypeIndex):
                texture = factory.CompositeCombinedTexture(self.name, mapTypeIndex, self.gender, self.buildDataManager, self._Doll__textureResolution[0], self._Doll__textureResolution[1], self.compressionSettings, self.overrideLod, mapBundle=self.mapBundle, textureCompositor=self.textureCompositor, partsToComposite=updateRuleBundle.partsToComposite)
                if texture:
                    self.mapBundle.SetMapByTypeIndex(mapTypeIndex, texture, hashKey)
                    didComposite = True


            self.SpawnUpdateChildTasklet(CompositeTasklet_t, mapTypeIndex)

        if (didComposite and ((not disableCaching) and factory.allowTextureCache)):
            factory.SaveMaps(hashKey, self.overrideLod, self.mapBundle)



    def ApplyShaders(self, meshes, wrinkleFx, factory):
        if not meshes:
            return 
        if self.IsInterior():
            ShaderApplyFun = self.SetInteriorShader
        else:
            ShaderApplyFun = self.SetExteriorShader
        for mesh in iter(meshes):
            isHair = False
            if (type(mesh) is trinity.Tr2ClothingActor):
                if (self.overrideLod <= 0):
                    fx = PD.GetEffectsFromMesh(mesh)
                    for f in iter(fx):
                        isHair = (PD.PortraitTools.BindHeroHairShader(f) or isHair)
                        if (self.overrideLod <= PD.LOD_SKIN):
                            PD.PortraitTools.BindHeroClothShader(f)

                if mesh.effect:
                    while mesh.effect.effectResource.isLoading:
                        blue.synchro.Yield()

                    mesh.effect.PopulateParameters()
                if mesh.effectReversed:
                    while mesh.effectReversed.effectResource.isLoading:
                        blue.synchro.Yield()

                    mesh.effectReversed.PopulateParameters()
                if (PD.SkinSpotLightShadows.instance is not None):
                    PD.SkinSpotLightShadows.instance.CreateEffectParamsForMesh(mesh, isClothMesh=True)
                if (isHair and hasattr(mesh, 'useTransparentBatches')):
                    mesh.useTransparentBatches = True
            elif (PD.DOLL_PARTS.HEAD in mesh.name):
                ShaderApplyFun(mesh, wrinkleFx=wrinkleFx, factory=factory)
            else:
                ShaderApplyFun(mesh, factory=factory)




    def SetExteriorShader(self, mesh, wrinkleFx = None, factory = None):
        fx = PD.GetEffectsFromMesh(mesh)
        for f in iter(fx):
            if (type(f) == trinity.Tr2ShaderMaterial):
                continue
            f.effectFilePath = PD.EXTERIOR_AVATAR_EFFECT_FILE_PATH




    def SetInteriorShader(self, mesh, wrinkleFx = None, factory = None):
        fx = PD.GetEffectsFromMesh(mesh)
        compSet = (self.compressionSettings if (self.compressionSettings is not None) else factory)
        self.useDXT5N = (compSet and (compSet.compressTextures and compSet.AllowCompress(PD.NORMAL_MAP)))
        for f in iter(fx):
            if (type(f) == trinity.Tr2ShaderMaterial):
                continue
            path = f.effectFilePath.lower()
            if ('masked.fx' in path):
                continue
            name = f.name.lower()
            if (name.startswith('c_custom') or name.startswith('c_s2')):
                PD.PortraitTools.BindCustomShaders(f, useFastShaders=self._Doll__useFastShader)
                if (PD.SkinSpotLightShadows.instance is not None):
                    PD.SkinSpotLightShadows.instance.CreateEffectParamsForMesh(mesh)
                else:
                    continue
            if (PD.DOLL_PARTS.HAIR not in path):
                if (self.overrideLod >= 0):
                    if ('skinnedavatarbrdf' not in path):
                        f.effectFilePath = PD.INTERIOR_AVATAR_EFFECT_FILE_PATH
                        foundBRDF = False
                        for r in f.resources:
                            if (r.name == 'FresnelLookupMap'):
                                foundBRDF = True
                            else:
                                continue

                        if not foundBRDF:
                            res = trinity.TriTexture2DParameter()
                            res.name = 'FresnelLookupMap'
                            res.resourcePath = PD.FRESNEL_LOOKUP_MAP
                            f.resources.append(res)
                elif (self.overrideLod == PD.LOD_HERO):
                    PD.PortraitTools.BindHeroShader(f, self.isOptimized)
                elif (self.overrideLod == PD.LOD_SKIN):
                    PD.PortraitTools.BindSkinShader(f, wrinkleFx, scattering=False, buildDataManager=self.buildDataManager, gender=self.gender, use_png=PD.USE_PNG)
                    PD.PortraitTools.BindLinearAvatarBRDF(f)
                else:
                    if (self.overrideLod <= PD.LOD_SCATTER_SKIN):
                        PD.PortraitTools.BindSkinShader(f, wrinkleFx, scattering=True, buildDataManager=self.buildDataManager, gender=self.gender, use_png=PD.USE_PNG)
                        PD.PortraitTools.BindLinearAvatarBRDF(f)
            elif (self.overrideLod <= 0):
                PD.PortraitTools.BindHeroHairShader(f)
            path = f.effectFilePath.lower()
            if (self._Doll__useFastShader and (('_fast.fx' not in path) and (path in PD.SHADERS_THAT_CAN_SWITCH_TO_FAST_SHADER_MODE))):
                f.effectFilePath = (f.effectFilePath[:-3] + '_fast.fx')
            if (self.useDXT5N and (path in PD.SHADERS_TO_ENABLE_DXT5N)):
                f.effectFilePath = (f.effectFilePath[:-3] + '_dxt5n.fx')
            if (PD.SkinSpotLightShadows.instance is not None):
                PD.SkinSpotLightShadows.instance.CreateEffectParamsForMesh(mesh)
            if (self.skinLightmapRenderer is not None):
                self.skinLightmapRenderer.BindLightmapShader(mesh)




    def LoadBindPose(self):
        filename = 'res:/Graphics/Character/Global/FaceSetup/BaseFemaleBindPose.yaml'
        if (self.gender == PD.GENDER.MALE):
            filename = 'res:/Graphics/Character/Global/FaceSetup/BaseMaleBindPose.yaml'
        self.bindPose = PD.LoadYamlFileNicely(filename)



    def LoadClothData(self, modifierList):
        clothMeshes = []
        for modifier in iter(modifierList):
            if (modifier.categorie in self.hiddenCategories):
                continue
            clothLoadPath = modifier.clothPath
            if not clothLoadPath:
                modifier.clothData = None
                continue
            if (self.overrideLod > 0):
                clothLod = clothLoadPath.replace('.red', (('_lod' + str(self.overrideLod)) + '.red'))
                if self.lodsInSeparateFolder:
                    (nonLodFolder, lodFolder,) = self.GetLODPathsToReplace()
                    clothLod = clothLod.lower().replace(nonLodFolder, lodFolder)
                r = blue.ResFile()
                if r.FileExists(clothLod):
                    clothLoadPath = clothLod
                else:
                    clothLoadPath = None
            if clothLoadPath:
                clothData = blue.os.LoadObject(clothLoadPath)
                blue.synchro.Yield()
                if (clothData is not None):
                    modifier.clothData = clothData
                clothMeshes.append(clothData)

        return clothMeshes



    def SetTextureSize(self, x, y):
        self._Doll__textureResolution[0] = x
        self._Doll__textureResolution[1] = y
        self.mapBundle.ReCreate()
        self.buildDataManager.SetAllAsDirty()
        if (self.skinLightmapRenderer is not None):
            self.skinLightmapRenderer.Refresh()



    def getbusyUpdating(self):
        return (self._currentUpdateTasklet is not None)


    busyUpdating = property(fget=lambda self: self.getbusyUpdating())

    def setoverrideLod(self, newLod):
        oldLod = self._Doll__overrideLod
        self._Doll__overrideLod = newLod
        if ((oldLod == PD.LOD_SKIN) and (newLod <= PD.LOD_SCATTER_SKIN)):
            self.buildDataManager.SetAllAsDirty()
            return 
        if ((oldLod <= PD.LOD_SCATTER_SKIN) and (newLod >= PD.LOD_SKIN)):
            self.skinLightmapRenderer = None
            if (newLod == PD.LOD_SKIN):
                return 
        self.mapBundle.ReCreate()
        self.buildDataManager.SetAllAsDirty(clearMeshes=True)


    overrideLod = property(fget=lambda self: self._Doll__overrideLod, fset=setoverrideLod)

    def settextureResolution(self, value):
        self.SetTextureSize(value[0], value[1])


    textureResolution = property(fget=lambda self: self._Doll__textureResolution, fset=settextureResolution)

    def setusecachedrendertargets(self, value):
        log.LogWarn('PaperDoll:Doll:UseCachedRenderTargets are depricated, this call does nothing.')


    useCachedRenderTargets = property(fget=lambda self: False, fset=setusecachedrendertargets)

    def setusefastshader(self, value):
        self._Doll__useFastShader = value
        self.buildDataManager.SetAllAsDirty()


    useFastShader = property(fget=lambda self: self._Doll__useFastShader, fset=setusefastshader)


